diff --git a/Refhub/Data/Seed/DataSeeder.cs b/Refhub/Data/Seed/DataSeeder.cs new file mode 100644 index 00000000..8468f3bc --- /dev/null +++ b/Refhub/Data/Seed/DataSeeder.cs @@ -0,0 +1,119 @@ +using Microsoft.EntityFrameworkCore; +using Refhub.Data.Context; +using Refhub.Data.Models; +using Refhub.Models.DTO; + +namespace Refhub.Data.Seed +{ + public static class DataSeeder + { + public static void SeedInitialData(IServiceProvider serviceProvider) + { + try + { + using var scope = serviceProvider.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + + db.Database.Migrate(); + + // Check if data already exists to avoid duplicates + if (db.Authors.Any()) + { + Console.WriteLine("Database already seeded. Skipping..."); + return; + } + + // Author + var authorPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "AuthorData.xlsx"); + if (File.Exists(authorPath)) + { + var authors = ExcelSeeder.ReadAuthorsFromExcel(authorPath); + if (authors.Any()) + { + db.Authors.AddRange(authors); + } + } + + // Category (seed before books since books reference categories) + var categoryPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "CategoryData.xlsx"); + if (File.Exists(categoryPath)) + { + var categories = ExcelSeeder.ReadCategoryFromExcel(categoryPath); + if (categories.Any()) + { + db.Categories.AddRange(categories); + } + } + + // Keyword (seed before BookKeyword) + var keywordPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "KeywordData.xlsx"); + if (File.Exists(keywordPath)) + { + var keywords = ExcelSeeder.ReadKeywordFromExcel(keywordPath); + if (keywords.Any()) + { + db.Keywords.AddRange(keywords); + } + } + + // Save basic entities first + db.SaveChanges(); + + // Book + var bookPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookData.xlsx"); + if (File.Exists(bookPath)) + { + var books = ExcelSeeder.ReadBooksFromExcel(bookPath); + if (books.Any()) + { + db.Books.AddRange(books); + } + } + + // Save books before relationships + db.SaveChanges(); + + // AuthorBook + var authorBookPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "AuthorBookData.xlsx"); + if (File.Exists(authorBookPath)) + { + var authorBooks = ExcelSeeder.ReadBookAuthorFromExcel(authorBookPath); + if (authorBooks.Any()) + { + db.BookAuthors.AddRange(authorBooks); + } + } + + // BookKeyword + var bookKeywordPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookKeywordData.xlsx"); + if (File.Exists(bookKeywordPath)) + { + var bookKeywords = ExcelSeeder.ReadBookKeywordFromExcel(bookKeywordPath); + if (bookKeywords.Any()) + { + db.BookKeywords.AddRange(bookKeywords); + } + } + + // BookRelation + var bookRelationPath = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookRelationData.xlsx"); + if (File.Exists(bookRelationPath)) + { + var bookRelations = ExcelSeeder.ReadBookRelationFromExcel(bookRelationPath); + if (bookRelations.Any()) + { + db.BookRelations.AddRange(bookRelations); + } + } + + db.SaveChanges(); + Console.WriteLine("Database seeding completed successfully."); + } + catch (Exception ex) + { + Console.WriteLine($"Error during database seeding: {ex.Message}"); + throw; + } + } + } +} diff --git a/Refhub/Data/Seed/ExcelSeeder.cs b/Refhub/Data/Seed/ExcelSeeder.cs new file mode 100644 index 00000000..3229479f --- /dev/null +++ b/Refhub/Data/Seed/ExcelSeeder.cs @@ -0,0 +1,313 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Refhub.Data.Models; +using OfficeOpenXml; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; + +namespace Refhub.Data.Seed +{ + public static class ExcelSeeder + { + static ExcelSeeder() + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } + + public static void License() + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + } + + + // Read Data from Author File + public static List ReadAuthorsFromExcel(string filePath) + { + try + { + License(); + var authors = new List(); + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + int rowCount = workSheet.Dimension.Rows; + + for (int row = 2; row <= rowCount; row++) + { + var fullName = workSheet.Cells[row, 1].Text; + var slug = workSheet.Cells[row, 2].Text; + + if (!String.IsNullOrWhiteSpace(fullName) && !String.IsNullOrWhiteSpace(slug)) + { + authors.Add(new Author + { + FullName = fullName, + Slug = slug + }); + } + } + } + return authors; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading authors from Excel: {ex.Message}", ex); + } + } + + // Read Data from Book File + public static List ReadBooksFromExcel(string filePath) + { + try + { + License(); + var books = new List(); + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + int rowCount = workSheet.Dimension.Rows; + + for (int row = 2; row <= rowCount; row++) + { + var title = workSheet.Cells[row, 1].Text; + var slug = workSheet.Cells[row, 2].Text; + var pageCountText = workSheet.Cells[row, 3].Text; + var filePathCell = workSheet.Cells[row, 4].Text; + var imagePath = workSheet.Cells[row, 5].Text; + var categoryIdText = workSheet.Cells[row, 6].Text; + var userId = workSheet.Cells[row, 7].Text; + + if (string.IsNullOrWhiteSpace(title) || string.IsNullOrWhiteSpace(slug)) + continue; + + int.TryParse(pageCountText, out int pageCount); + int.TryParse(categoryIdText, out int categoryId); + + books.Add(new Book + { + Title = title, + Slug = slug, + PageCount = pageCount, + FilePath = filePathCell, + ImagePath = imagePath, + CategoryId = categoryId, + UserId = string.IsNullOrWhiteSpace(userId) ? null : userId + }); + } + } + return books; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading books from Excel: {ex.Message}", ex); + } + } + + // Read Data from BookAuthor Excel file + public static List ReadBookAuthorFromExcel(string filePath) + { + try + { + License(); + var bookAuthors = new List(); + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + int rowCount = workSheet.Dimension.Rows; + for (int row = 2; row <= rowCount; row++) + { + var authorId = workSheet.Cells[row, 1].Text; + var bookId = workSheet.Cells[row, 2].Text; + + if (!String.IsNullOrWhiteSpace(authorId) && !String.IsNullOrWhiteSpace(bookId) + && int.TryParse(authorId, out int authorIdValue) && int.TryParse(bookId, out int bookIdValue)) + { + bookAuthors.Add(new BookAuthor + { + AuthorId = authorIdValue, + BookId = bookIdValue + }); + } + } + } + return bookAuthors; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading book authors from Excel: {ex.Message}", ex); + } + } + + // Read Data from KeyWord Excel file + public static List ReadKeywordFromExcel(string filePath) + { + try + { + License(); + var keywords = new List(); + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + int rowCount = workSheet.Dimension.Rows; + for (int row = 2; row <= rowCount; row++) + { + var keyword = workSheet.Cells[row, 1].Text; + + if (!String.IsNullOrWhiteSpace(keyword)) + { + keywords.Add(new Keyword + { + Word = keyword, + }); + } + } + } + return keywords; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading keywords from Excel: {ex.Message}", ex); + } + } + + // Read Data from Category Excel file + public static List ReadCategoryFromExcel(string filePath) + { + try + { + License(); + var categories = new List(); + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + int rowCount = workSheet.Dimension.Rows; + + for (int row = 2; row <= rowCount; row++) + { + var name = workSheet.Cells[row, 1].Text; + var slug = workSheet.Cells[row, 2].Text; + var description = workSheet.Cells[row, 3].Text; + + if (!String.IsNullOrWhiteSpace(name) && !String.IsNullOrWhiteSpace(slug) && !String.IsNullOrWhiteSpace(description)) + { + categories.Add(new Category + { + Name = name, + Description = description, + slug = slug + }); + } + } + } + return categories; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading categories from Excel: {ex.Message}", ex); + } + } + + // Read Data from BookRelation Excel file + public static List ReadBookRelationFromExcel(string filePath) + { + try + { + License(); + var bookRelations = new List(); + + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + var rowCount = workSheet.Dimension.Rows; + + for (int row = 2; row <= rowCount; row++) + { + var bookId = workSheet.Cells[row, 1].Text; + var relationBookId = workSheet.Cells[row, 2].Text; + + if (!String.IsNullOrWhiteSpace(bookId) && !String.IsNullOrWhiteSpace(relationBookId) + && int.TryParse(bookId, out int bookIdValue) && int.TryParse(relationBookId, out int relationBookIdValue)) + { + bookRelations.Add(new BookRelation + { + BookId = bookIdValue, + RelatedBookId = relationBookIdValue + }); + } + } + } + return bookRelations; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading book relations from Excel: {ex.Message}", ex); + } + } + + // Read Data from BookKeyword Excel file + public static List ReadBookKeywordFromExcel(string filePath) + { + try + { + License(); + var keywords = new List(); + using (var package = new ExcelPackage(new FileInfo(filePath))) + { + var workSheet = package.Workbook.Worksheets[0]; + var rowCount = workSheet.Dimension.Rows; + + for (int row = 2; row <= rowCount; row++) + { + var bookId = workSheet.Cells[row, 1].Text; + var keywordId = workSheet.Cells[row, 2].Text; + + if (!String.IsNullOrWhiteSpace(bookId) && !String.IsNullOrWhiteSpace(keywordId) + && int.TryParse(bookId, out int bookIdValue) && int.TryParse(keywordId, out int keywordIdValue)) + { + keywords.Add(new BookKeyword + { + BookId = bookIdValue, + KeywordId = keywordIdValue + }); + } + } + } + return keywords; + } + catch (FileNotFoundException) + { + throw new InvalidOperationException($"Excel file not found: {filePath}"); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Error reading book keywords from Excel: {ex.Message}", ex); + } + } + } +} \ No newline at end of file diff --git a/Refhub/Migrations/20250616145440_AddAuthorTable.Designer.cs b/Refhub/Migrations/20250616145440_AddAuthorTable.Designer.cs new file mode 100644 index 00000000..fe75f4b8 --- /dev/null +++ b/Refhub/Migrations/20250616145440_AddAuthorTable.Designer.cs @@ -0,0 +1,541 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Refhub.Data.Context; + +#nullable disable + +namespace Refhub.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250616145440_AddAuthorTable")] + partial class AddAuthorTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)"); + + b.Property("Name") + .HasColumnType("nvarchar(450)"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Refhub.Data.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Refhub.Data.Models.Author", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("FullName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Authors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("FilePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PageCount") + .HasColumnType("int"); + + b.Property("Slug") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(155) + .HasColumnType("nvarchar(155)"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("Slug") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookAuthor", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("AuthorId") + .HasColumnType("int"); + + b.HasKey("BookId", "AuthorId"); + + b.HasIndex("AuthorId"); + + b.ToTable("BookAuthors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookKeyword", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("KeywordId") + .HasColumnType("int"); + + b.HasKey("BookId", "KeywordId"); + + b.HasIndex("KeywordId"); + + b.ToTable("BookKeywords"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookRelation", b => + { + b.Property("BookId") + .HasColumnType("int"); + + b.Property("RelatedBookId") + .HasColumnType("int"); + + b.HasKey("BookId", "RelatedBookId"); + + b.HasIndex("RelatedBookId"); + + b.ToTable("BookRelations"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("slug") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Keyword", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Word") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.HasKey("Id"); + + b.ToTable("Keywords"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Refhub.Data.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.HasOne("Refhub.Data.Models.Category", "Category") + .WithMany("Books") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.ApplicationUser", "User") + .WithMany("Books") + .HasForeignKey("UserId"); + + b.Navigation("Category"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookAuthor", b => + { + b.HasOne("Refhub.Data.Models.Author", "Author") + .WithMany("BookAuthors") + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("BookAuthors") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Author"); + + b.Navigation("Book"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookKeyword", b => + { + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("BookKeywords") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Keyword", "Keyword") + .WithMany("BookKeywords") + .HasForeignKey("KeywordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("Keyword"); + }); + + modelBuilder.Entity("Refhub.Data.Models.BookRelation", b => + { + b.HasOne("Refhub.Data.Models.Book", "Book") + .WithMany("RelatedTo") + .HasForeignKey("BookId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Refhub.Data.Models.Book", "RelatedBook") + .WithMany("RelatedFrom") + .HasForeignKey("RelatedBookId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Book"); + + b.Navigation("RelatedBook"); + }); + + modelBuilder.Entity("Refhub.Data.Models.ApplicationUser", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Author", b => + { + b.Navigation("BookAuthors"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Book", b => + { + b.Navigation("BookAuthors"); + + b.Navigation("BookKeywords"); + + b.Navigation("RelatedFrom"); + + b.Navigation("RelatedTo"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Category", b => + { + b.Navigation("Books"); + }); + + modelBuilder.Entity("Refhub.Data.Models.Keyword", b => + { + b.Navigation("BookKeywords"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Refhub/Migrations/20250616145440_AddAuthorTable.cs b/Refhub/Migrations/20250616145440_AddAuthorTable.cs new file mode 100644 index 00000000..e11685f1 --- /dev/null +++ b/Refhub/Migrations/20250616145440_AddAuthorTable.cs @@ -0,0 +1,75 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Refhub.Migrations +{ + /// + public partial class AddAuthorTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Slug", + table: "Books", + type: "nvarchar(450)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateIndex( + name: "IX_Books_Slug", + table: "Books", + column: "Slug", + unique: true); + + migrationBuilder.CreateTable( + name: "Authors", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + FullName = table.Column(maxLength: 256, nullable: false), + Slug = table.Column(nullable: false) + }, + constraints: table => table.PrimaryKey("PK_Authors", x => x.Id)); + + migrationBuilder.CreateTable( + name: "BookAuthors", + columns: table => new + { + BookId = table.Column(nullable: false), + AuthorId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BookAuthors", x => new { x.BookId, x.AuthorId }); + table.ForeignKey("FK_BookAuthors_Books_BookId", x => x.BookId, "Books", "Id", onDelete: ReferentialAction.Cascade); + table.ForeignKey("FK_BookAuthors_Authors_AuthorId", x => x.AuthorId, "Authors", "Id", onDelete: ReferentialAction.Cascade); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BookAuthors"); + + migrationBuilder.DropTable( + name: "Authors"); + + migrationBuilder.DropIndex( + name: "IX_Books_Slug", + table: "Books"); + + migrationBuilder.AlterColumn( + name: "Slug", + table: "Books", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(450)"); + } + } +} diff --git a/Refhub/Program.cs b/Refhub/Program.cs index f7a1efc9..5182f610 100644 --- a/Refhub/Program.cs +++ b/Refhub/Program.cs @@ -1,5 +1,7 @@ using Microsoft.EntityFrameworkCore; using Refhub.Data.Context; +using Refhub.Data.Models; +using Refhub.Data.Seed; using Refhub.Tools.ExtensionMethod; using Refhub.Tools.Utilities; @@ -75,6 +77,17 @@ public static void Main(string[] args) // Configure browser launching based on environment variables app.UseBrowserLaunchMode(); + // Seed initial data from Excel files + try + { + DataSeeder.SeedInitialData(app.Services); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to seed data: {ex.Message}"); + // Continue startup even if seeding fails in development + } + app.Run(); } } diff --git a/Refhub/Refhub.csproj b/Refhub/Refhub.csproj index 041e873a..0d00478d 100644 --- a/Refhub/Refhub.csproj +++ b/Refhub/Refhub.csproj @@ -14,6 +14,7 @@ + @@ -41,4 +42,13 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/Refhub/SeedData/AuthorBookData.xlsx b/Refhub/SeedData/AuthorBookData.xlsx new file mode 100644 index 00000000..a4512992 Binary files /dev/null and b/Refhub/SeedData/AuthorBookData.xlsx differ diff --git a/Refhub/SeedData/AuthorData.xlsx b/Refhub/SeedData/AuthorData.xlsx new file mode 100644 index 00000000..19fc1e27 Binary files /dev/null and b/Refhub/SeedData/AuthorData.xlsx differ diff --git a/Refhub/SeedData/BookData.xlsx b/Refhub/SeedData/BookData.xlsx new file mode 100644 index 00000000..390ca1fe Binary files /dev/null and b/Refhub/SeedData/BookData.xlsx differ diff --git a/Refhub/SeedData/BookKeywordData.xlsx b/Refhub/SeedData/BookKeywordData.xlsx new file mode 100644 index 00000000..262d8d53 Binary files /dev/null and b/Refhub/SeedData/BookKeywordData.xlsx differ diff --git a/Refhub/SeedData/BookRelationData.xlsx b/Refhub/SeedData/BookRelationData.xlsx new file mode 100644 index 00000000..a9a1790b Binary files /dev/null and b/Refhub/SeedData/BookRelationData.xlsx differ diff --git a/Refhub/SeedData/CategoryData.xlsx b/Refhub/SeedData/CategoryData.xlsx new file mode 100644 index 00000000..f4804920 Binary files /dev/null and b/Refhub/SeedData/CategoryData.xlsx differ diff --git a/Refhub/SeedData/KeywordData.xlsx b/Refhub/SeedData/KeywordData.xlsx new file mode 100644 index 00000000..c42e37b6 Binary files /dev/null and b/Refhub/SeedData/KeywordData.xlsx differ