diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs new file mode 100644 index 0000000..d4850f0 --- /dev/null +++ b/Data/ApplicationDbContext.cs @@ -0,0 +1,29 @@ +// using Microsoft.AspNetCore.Identity; +// using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using QuotifyBE.Entities; + +namespace QuotifyBE.Data +{ + public class ApplicationDbContext : DbContext //, string> + { + public ApplicationDbContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Users => Set(); + public DbSet Quotes => Set(); + public DbSet Categories => Set(); + public DbSet Images => Set(); + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity() + .HasKey(vs => new { vs.QuoteId, vs.CategoryId }); + } + } +} \ No newline at end of file diff --git a/Entities/Category.cs b/Entities/Category.cs new file mode 100644 index 0000000..b168608 --- /dev/null +++ b/Entities/Category.cs @@ -0,0 +1,8 @@ +namespace QuotifyBE.Entities +{ + public class Category + { + public int Id { get; set; } + public string? Name { get; set; } + } +} diff --git a/Entities/Image.cs b/Entities/Image.cs new file mode 100644 index 0000000..206b648 --- /dev/null +++ b/Entities/Image.cs @@ -0,0 +1,8 @@ +namespace QuotifyBE.Entities +{ + public class Image + { + public int Id { get; set; } + public string? Url { get; set; } + } +} diff --git a/Entities/Quote.cs b/Entities/Quote.cs new file mode 100644 index 0000000..896c5f6 --- /dev/null +++ b/Entities/Quote.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace QuotifyBE.Entities +{ + public class Quote + { + public int Id { get; set; } + required public string Text { get; set; } + required public string Author { get; set; } + //public int CategoryId { get; set; } + public int ImageId { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime LastUpdatedAt { get; set; } + public int UserId { get; set; } + + public User? User { get; set; } + public ICollection? QuoteCategories = new List(); + } +} diff --git a/Entities/QuoteCategory.cs b/Entities/QuoteCategory.cs new file mode 100644 index 0000000..b5b30a7 --- /dev/null +++ b/Entities/QuoteCategory.cs @@ -0,0 +1,11 @@ +namespace QuotifyBE.Entities +{ + public class QuoteCategory + { + public int QuoteId { get; set; } + public int CategoryId { get; set; } + + public Quote? Quote { get; set; } + public Category? Category { get; set; } + } +} diff --git a/Entities/User.cs b/Entities/User.cs new file mode 100644 index 0000000..5ddf67f --- /dev/null +++ b/Entities/User.cs @@ -0,0 +1,10 @@ +namespace QuotifyBE.Entities +{ + public class User + { + public int Id { get; set; } + required public string Name { get; set; } + required public string Email { get; set; } + required public string PasswordHash { get; set; } + } +} diff --git a/Migrations/20250714093636_initial_migration.Designer.cs b/Migrations/20250714093636_initial_migration.Designer.cs new file mode 100644 index 0000000..c96b12b --- /dev/null +++ b/Migrations/20250714093636_initial_migration.Designer.cs @@ -0,0 +1,167 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using QuotifyBE.Data; + +#nullable disable + +namespace QuotifyBE.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250714093636_initial_migration")] + partial class initial_migration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("QuotifyBE.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Url") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageId") + .HasColumnType("integer"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.QuoteCategory", b => + { + b.Property("QuoteId") + .HasColumnType("integer"); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.HasKey("QuoteId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("QuoteCategory"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Quote", b => + { + b.HasOne("QuotifyBE.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.QuoteCategory", b => + { + b.HasOne("QuotifyBE.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("QuotifyBE.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Quote"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20250714093636_initial_migration.cs b/Migrations/20250714093636_initial_migration.cs new file mode 100644 index 0000000..8806415 --- /dev/null +++ b/Migrations/20250714093636_initial_migration.cs @@ -0,0 +1,134 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace QuotifyBE.Migrations +{ + /// + public partial class initial_migration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Images", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Url = table.Column(type: "text", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Images", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Name = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + PasswordHash = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Quotes", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + Text = table.Column(type: "text", nullable: false), + Author = table.Column(type: "text", nullable: false), + ImageId = table.Column(type: "integer", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false), + LastUpdatedAt = table.Column(type: "timestamp with time zone", nullable: false), + UserId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Quotes", x => x.Id); + table.ForeignKey( + name: "FK_Quotes_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "QuoteCategory", + columns: table => new + { + QuoteId = table.Column(type: "integer", nullable: false), + CategoryId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_QuoteCategory", x => new { x.QuoteId, x.CategoryId }); + table.ForeignKey( + name: "FK_QuoteCategory_Categories_CategoryId", + column: x => x.CategoryId, + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_QuoteCategory_Quotes_QuoteId", + column: x => x.QuoteId, + principalTable: "Quotes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_QuoteCategory_CategoryId", + table: "QuoteCategory", + column: "CategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_Quotes_UserId", + table: "Quotes", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Images"); + + migrationBuilder.DropTable( + name: "QuoteCategory"); + + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Quotes"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/Migrations/ApplicationDbContextModelSnapshot.cs b/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..2af654c --- /dev/null +++ b/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,164 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using QuotifyBE.Data; + +#nullable disable + +namespace QuotifyBE.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("QuotifyBE.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Url") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ImageId") + .HasColumnType("integer"); + + b.Property("LastUpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Text") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.QuoteCategory", b => + { + b.Property("QuoteId") + .HasColumnType("integer"); + + b.Property("CategoryId") + .HasColumnType("integer"); + + b.HasKey("QuoteId", "CategoryId"); + + b.HasIndex("CategoryId"); + + b.ToTable("QuoteCategory"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.Quote", b => + { + b.HasOne("QuotifyBE.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("QuotifyBE.Entities.QuoteCategory", b => + { + b.HasOne("QuotifyBE.Entities.Category", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("QuotifyBE.Entities.Quote", "Quote") + .WithMany() + .HasForeignKey("QuoteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Quote"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Program.cs b/Program.cs index 48863a6..5ecae42 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using QuotifyBE.Data; +using QuotifyBE.Entities; + var builder = WebApplication.CreateBuilder(args); +// Configure Database Conecction +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); +builder.Services.AddDbContext(options => + options.UseNpgsql(connectionString)); + // Add services to the container. builder.Services.AddControllers(); @@ -12,6 +21,7 @@ var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { + app.UseMigrationsEndPoint(); app.UseSwagger(); app.UseSwaggerUI(); } diff --git a/QuotifyBE.csproj b/QuotifyBE.csproj index 51579d7..9ea07e8 100644 --- a/QuotifyBE.csproj +++ b/QuotifyBE.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -10,8 +10,19 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/appsettings.example.json b/appsettings.example.json index 10f68b8..4a5ad9e 100644 --- a/appsettings.example.json +++ b/appsettings.example.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "DefaultConnection": "Server=server-host;Database=db-name;Username=quotify-user;Password=user-secret" + }, "Logging": { "LogLevel": { "Default": "Information",