From 2551fe181057e865f41be076b836f1fa8dbf984b Mon Sep 17 00:00:00 2001 From: azeez-odetola Date: Mon, 8 Dec 2025 22:02:34 +0100 Subject: [PATCH 1/3] done with a little help from gpt --- CodeReviews.Console.ExcelReader.sln | 24 ++++++++ .../Data/AppContext.cs | 12 ++++ .../DocumentProcessor.yemiodetola.csproj | 26 +++++++++ ...251208000159_pendingMigrations.Designer.cs | 46 +++++++++++++++ .../20251208000159_pendingMigrations.cs | 36 ++++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 43 ++++++++++++++ .../Models/Contact.cs | 31 ++++++++++ DocumentProcessor.yemiodetola/Program.cs | 46 +++++++++++++++ DocumentProcessor.yemiodetola/README.md | 19 +++++++ .../Services/ExcelReader.cs | 33 +++++++++++ .../Services/ExcelService.cs | 12 ++++ .../Services/IExcelService.cs | 8 +++ .../Services/IPdfService.cs | 8 +++ .../Services/PdfService.cs | 53 ++++++++++++++++++ .../Services/PdfServiceFixed.cs | 16 ++++++ DocumentProcessor.yemiodetola/excel_data.db | Bin 0 -> 24576 bytes DocumentProcessor.yemiodetola/sample.xlsx | Bin 0 -> 4969 bytes 17 files changed, 413 insertions(+) create mode 100644 CodeReviews.Console.ExcelReader.sln create mode 100644 DocumentProcessor.yemiodetola/Data/AppContext.cs create mode 100644 DocumentProcessor.yemiodetola/DocumentProcessor.yemiodetola.csproj create mode 100644 DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs create mode 100644 DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.cs create mode 100644 DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs create mode 100644 DocumentProcessor.yemiodetola/Models/Contact.cs create mode 100644 DocumentProcessor.yemiodetola/Program.cs create mode 100644 DocumentProcessor.yemiodetola/README.md create mode 100644 DocumentProcessor.yemiodetola/Services/ExcelReader.cs create mode 100644 DocumentProcessor.yemiodetola/Services/ExcelService.cs create mode 100644 DocumentProcessor.yemiodetola/Services/IExcelService.cs create mode 100644 DocumentProcessor.yemiodetola/Services/IPdfService.cs create mode 100644 DocumentProcessor.yemiodetola/Services/PdfService.cs create mode 100644 DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs create mode 100644 DocumentProcessor.yemiodetola/excel_data.db create mode 100644 DocumentProcessor.yemiodetola/sample.xlsx diff --git a/CodeReviews.Console.ExcelReader.sln b/CodeReviews.Console.ExcelReader.sln new file mode 100644 index 0000000..2ded134 --- /dev/null +++ b/CodeReviews.Console.ExcelReader.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentProcessor.yemiodetola", "DocumentProcessor.yemiodetola\DocumentProcessor.yemiodetola.csproj", "{C9D0FBE3-174F-6FCE-D2C4-7B102ACA0948}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9D0FBE3-174F-6FCE-D2C4-7B102ACA0948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9D0FBE3-174F-6FCE-D2C4-7B102ACA0948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9D0FBE3-174F-6FCE-D2C4-7B102ACA0948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9D0FBE3-174F-6FCE-D2C4-7B102ACA0948}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7166A67F-77F9-4000-A6B5-FB0681427764} + EndGlobalSection +EndGlobal diff --git a/DocumentProcessor.yemiodetola/Data/AppContext.cs b/DocumentProcessor.yemiodetola/Data/AppContext.cs new file mode 100644 index 0000000..dbbe82d --- /dev/null +++ b/DocumentProcessor.yemiodetola/Data/AppContext.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; +using DocumentProcessor.yemiodetola.Models; +public class AppDbContext : DbContext +{ + public DbSet Contacts => Set(); + + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + var dbName = System.AppContext.GetData("SqliteDbName") as string ?? "excel_data.db"; + options.UseSqlite($"Data Source={dbName}"); + } +} \ No newline at end of file diff --git a/DocumentProcessor.yemiodetola/DocumentProcessor.yemiodetola.csproj b/DocumentProcessor.yemiodetola/DocumentProcessor.yemiodetola.csproj new file mode 100644 index 0000000..01c08f5 --- /dev/null +++ b/DocumentProcessor.yemiodetola/DocumentProcessor.yemiodetola.csproj @@ -0,0 +1,26 @@ + + + Exe + net9.0 + enable + enable + + + + ./output.pdf + excel_data.db + ./sample.xlsx + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + \ No newline at end of file diff --git a/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs new file mode 100644 index 0000000..7e3ea1c --- /dev/null +++ b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DocumentProcessor.yemiodetola.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20251208000159_pendingMigrations")] + partial class pendingMigrations + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("DocumentProcessor.yemiodetola.Models.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.cs b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.cs new file mode 100644 index 0000000..8f9009a --- /dev/null +++ b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DocumentProcessor.yemiodetola.Migrations +{ + /// + public partial class pendingMigrations : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Contacts", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", nullable: false), + Email = table.Column(type: "TEXT", nullable: false), + PhoneNumber = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Contacts", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Contacts"); + } + } +} diff --git a/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs b/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..0a73941 --- /dev/null +++ b/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,43 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DocumentProcessor.yemiodetola.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("DocumentProcessor.yemiodetola.Models.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DocumentProcessor.yemiodetola/Models/Contact.cs b/DocumentProcessor.yemiodetola/Models/Contact.cs new file mode 100644 index 0000000..6e29f2b --- /dev/null +++ b/DocumentProcessor.yemiodetola/Models/Contact.cs @@ -0,0 +1,31 @@ +namespace DocumentProcessor.yemiodetola.Models; + +public class Contact +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string PhoneNumber { get; set; } = string.Empty; + + public bool validateEmailAddress(string email) + { + if (String.IsNullOrEmpty(email)) + { + return false; + } + int atIndex = email.IndexOf('@'); + int dotIndex = email.LastIndexOf('.'); + + return atIndex > 0 + && dotIndex > atIndex + 1 + && dotIndex < email.Length - 1; + } + + public bool validatePhoneNumber(string phoneNumber) + { + return !String.IsNullOrEmpty(phoneNumber) + && phoneNumber.Length == 11 + && phoneNumber.StartsWith("0") + && phoneNumber.All(char.IsDigit); + } +} \ No newline at end of file diff --git a/DocumentProcessor.yemiodetola/Program.cs b/DocumentProcessor.yemiodetola/Program.cs new file mode 100644 index 0000000..0730c39 --- /dev/null +++ b/DocumentProcessor.yemiodetola/Program.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.DependencyInjection; +using System.Threading.Tasks; +using QuestPDF.Infrastructure; +using DocumentProcessor.yemiodetola.Services; + +class Program +{ + static async Task Main(string[] args) + { + QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community; + + var services = new ServiceCollection(); + + services.AddSingleton(); + services.AddSingleton(); + + var provider = services.BuildServiceProvider(); + + var excelService = provider.GetRequiredService(); + var pdfService = provider.GetRequiredService(); + + var excelPath = AppContext.GetData("ExcelFilePath") as string ?? "./sample.xlsx"; + var rows = await excelService.ReadExcelAsync(excelPath); + + if (rows == null || rows.Count == 0) + { + Console.WriteLine("Excel file is empty or unreadable."); + return; + } + + Console.WriteLine("\nExcel File Contents:\n"); + Console.WriteLine($"{ "Name",-25} { "Email",-35} { "Phone Number",-15}"); + Console.WriteLine(new string('-', 80)); + foreach (var contact in rows) + { + Console.WriteLine($"{contact.Name,-25} {contact.Email,-35} {contact.PhoneNumber,-15}"); + } + Console.WriteLine(new string('-', 80)); + + var outputPdf = AppContext.GetData("PdfOutputPath") as string ?? "./output.pdf"; + + await pdfService.GeneratePdfAsync(outputPdf, rows); + + Console.WriteLine($"PDF generated successfully at: {outputPdf}"); + } +} diff --git a/DocumentProcessor.yemiodetola/README.md b/DocumentProcessor.yemiodetola/README.md new file mode 100644 index 0000000..b286eae --- /dev/null +++ b/DocumentProcessor.yemiodetola/README.md @@ -0,0 +1,19 @@ +# DocumentProcessor Console App + +## Setup Instructions + +1. **Clone the repository** + ```sh + cd DocumentProcessor.yemiodetola + ``` + +2. **Add your Excel file** + - Place your Excel file (e.g., `sample.xlsx`) in the project directory (There's a sample currently). + - Ensure the worksheet is named `Contacts` and has columns: Name, Email, Phone Number. + +3. **Run the program** + ```sh + dotnet run + ``` + +Enjoy! diff --git a/DocumentProcessor.yemiodetola/Services/ExcelReader.cs b/DocumentProcessor.yemiodetola/Services/ExcelReader.cs new file mode 100644 index 0000000..dc3fa6f --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/ExcelReader.cs @@ -0,0 +1,33 @@ +using ClosedXML.Excel; +using DocumentProcessor.yemiodetola.Models; + +namespace DocumentProcessor.yemiodetola.Services; +public static class ExcelReader +{ + public static List ListContacts(string path, string sheet) + { + var results = new List(); + using var workbook = new XLWorkbook(path); + var ws = workbook.Worksheet(sheet); + + foreach (var row in ws.RowsUsed().Skip(1)) + { + var name = row.Cell(1).GetString().Trim(); + var email = row.Cell(2).GetString().Trim(); + var phone = row.Cell(3).GetString().Trim(); + + var model = new Contact + { + Name = name, + Email = email, + PhoneNumber = phone + }; + + if (!string.IsNullOrWhiteSpace(model.Name)) + { + results.Add(model); + } + } + return results; + } +} \ No newline at end of file diff --git a/DocumentProcessor.yemiodetola/Services/ExcelService.cs b/DocumentProcessor.yemiodetola/Services/ExcelService.cs new file mode 100644 index 0000000..85a965e --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/ExcelService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DocumentProcessor.yemiodetola.Models; +namespace DocumentProcessor.yemiodetola.Services; +public class ExcelService : IExcelService +{ + public async Task> ReadExcelAsync(string path) + { + // For demo, call ExcelReader synchronously + return await Task.Run(() => ExcelReader.ListContacts(path, "Contacts")); + } +} diff --git a/DocumentProcessor.yemiodetola/Services/IExcelService.cs b/DocumentProcessor.yemiodetola/Services/IExcelService.cs new file mode 100644 index 0000000..27b5ea8 --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/IExcelService.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DocumentProcessor.yemiodetola.Models; +namespace DocumentProcessor.yemiodetola.Services; +public interface IExcelService +{ + Task> ReadExcelAsync(string path); +} diff --git a/DocumentProcessor.yemiodetola/Services/IPdfService.cs b/DocumentProcessor.yemiodetola/Services/IPdfService.cs new file mode 100644 index 0000000..ff9eb4d --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/IPdfService.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DocumentProcessor.yemiodetola.Models; +namespace DocumentProcessor.yemiodetola.Services; +public interface IPdfService +{ + Task GeneratePdfAsync(string outputPath, List contacts); +} diff --git a/DocumentProcessor.yemiodetola/Services/PdfService.cs b/DocumentProcessor.yemiodetola/Services/PdfService.cs new file mode 100644 index 0000000..79dcc0d --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/PdfService.cs @@ -0,0 +1,53 @@ +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; +using DocumentProcessor.yemiodetola.Models; + +namespace DocumentProcessor.yemiodetola.Services; + +public class ContactsDocument : IDocument +{ + private readonly List _contacts; + public ContactsDocument(List contacts) + { + _contacts = contacts; + } + + public DocumentMetadata GetMetadata() => new DocumentMetadata(); + public DocumentSettings GetSettings() => new DocumentSettings(); + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(30); + + page.Header().Text("Contact List") + .FontSize(20) + .SemiBold() + .AlignCenter(); + + page.Content().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(150); + columns.RelativeColumn(); + columns.ConstantColumn(120); + }); + table.Header(header => + { + header.Cell().Text("Name").SemiBold(); + header.Cell().Text("Email").SemiBold(); + header.Cell().Text("Phone Number").SemiBold(); + }); + foreach (var c in _contacts) + { + table.Cell().Text(c.Name); + table.Cell().Text(c.Email); + table.Cell().Text(c.PhoneNumber); + } + }); + }); + } +} + diff --git a/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs b/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs new file mode 100644 index 0000000..573fe7a --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using QuestPDF.Fluent; +using DocumentProcessor.yemiodetola.Models; +namespace DocumentProcessor.yemiodetola.Services; +public class PdfService : IPdfService +{ + public async Task GeneratePdfAsync(string outputPath, List contacts) + { + await Task.Run(() => + { + var document = new ContactsDocument(contacts); + document.GeneratePdf(outputPath); + }); + } +} diff --git a/DocumentProcessor.yemiodetola/excel_data.db b/DocumentProcessor.yemiodetola/excel_data.db new file mode 100644 index 0000000000000000000000000000000000000000..7e33126105022c6aeba6c8d03d96f5d8acb245d4 GIT binary patch literal 24576 zcmeI)O;g%H7zgl;5v+_Dd*Z~*7A`tUX2Uy9528(5DnyF0Q>W8mB5WlsNtDDH?M?ep z`i*+%rM>jntDm47M5~m(96Z#S|4v|^Y&H+sAGs`KcKdnV4mf?|4IC?=IkG?`iL6md z2#Jnfq8b)iyibqfu#&FDzYC*8eSa8N@+30%jVKT2@6Uga=R}AC0SG_<0uX=z1Rwwb z2teTW1)7m)VtHBG4XnLB_YeDGw$t|c;SqN`b=YF5|lIvwk~=voe65oa!) zzM7s%B$LvI*I5(Nz!#S~mNm9ilcR~(C5hN>mmiP5Tk9zBhWqV_ zpVHPQHivWId*O|GT9sxJnkJoPCQ#MA&bynZhb>coLJB(wq4>YwC%=9`ie8r)r7~@Y z`+?;gjH$$9OYv|Z3xp_N!T<#V5P$##AOHafKmY;|fB*y_0D(Iukd|c9$g*r9qh&L! zOc`5c*;STTwPGn(tSpE7ES&#K%4Z@r6bL{70uX=z1Rwwb2tWV=5P$##?v%iyBsUV_ z)L+Z8GGk0DRN4pJ?b`1C)sq)~MP(|BiE&BFS0XkP2tWV=5P$##AOHafKmY;|fB*#U zpg>w~%>M01fbjo+SvereC*`B4P#^#S2tWV=5P$##AOHafKmY;|xCH@Cj*xUEUF|u% zOV@e7?^$7}?uFr6*At62xT-hUXJsv$%NL5JawRhrArErthBY{$+m0Rd91*F07_EhC zJg6PdVI@{6mx_gaF1sQ{NV1TA=Jj0q$m4HCt#&=W#*f7U0DV5}U1pcrFP{HDC(8LP m3=SzE009U<00Izz00bZa0SG_<0ucC51m+|;DP1lwDgOY97gQMl literal 0 HcmV?d00001 diff --git a/DocumentProcessor.yemiodetola/sample.xlsx b/DocumentProcessor.yemiodetola/sample.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b60e29c19b262a22d8018c1a75786ed2392ef78b GIT binary patch literal 4969 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 za2 Date: Mon, 8 Dec 2025 22:04:51 +0100 Subject: [PATCH 2/3] correction --- DocumentProcessor.yemiodetola/Services/ExcelService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/DocumentProcessor.yemiodetola/Services/ExcelService.cs b/DocumentProcessor.yemiodetola/Services/ExcelService.cs index 85a965e..b9ef55e 100644 --- a/DocumentProcessor.yemiodetola/Services/ExcelService.cs +++ b/DocumentProcessor.yemiodetola/Services/ExcelService.cs @@ -6,7 +6,6 @@ public class ExcelService : IExcelService { public async Task> ReadExcelAsync(string path) { - // For demo, call ExcelReader synchronously return await Task.Run(() => ExcelReader.ListContacts(path, "Contacts")); } } From d3f847a1ede0cb02e006ecd22ac6e317b89ab615 Mon Sep 17 00:00:00 2001 From: azeez-odetola Date: Mon, 8 Dec 2025 22:45:23 +0100 Subject: [PATCH 3/3] Fix: correction from copilot --- .../Data/AppContext.cs | 2 +- ...251208000159_pendingMigrations.Designer.cs | 2 +- .../20251208214440_contextupdate.Designer.cs | 46 ++++++++++++++ .../20251208214440_contextupdate.cs | 22 +++++++ .../Migrations/AppDbContextModelSnapshot.cs | 2 +- .../Models/Contact.cs | 4 +- DocumentProcessor.yemiodetola/Program.cs | 12 ++-- DocumentProcessor.yemiodetola/README.md | 2 +- .../Services/ContactsDocument.cs | 53 ++++++++++++++++ .../Services/PdfService.cs | 57 +++--------------- .../Services/PdfServiceFixed.cs | 16 ----- DocumentProcessor.yemiodetola/excel_data.db | Bin 24576 -> 24576 bytes 12 files changed, 142 insertions(+), 76 deletions(-) create mode 100644 DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.Designer.cs create mode 100644 DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.cs create mode 100644 DocumentProcessor.yemiodetola/Services/ContactsDocument.cs delete mode 100644 DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs diff --git a/DocumentProcessor.yemiodetola/Data/AppContext.cs b/DocumentProcessor.yemiodetola/Data/AppContext.cs index dbbe82d..bdd9d79 100644 --- a/DocumentProcessor.yemiodetola/Data/AppContext.cs +++ b/DocumentProcessor.yemiodetola/Data/AppContext.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; using DocumentProcessor.yemiodetola.Models; -public class AppDbContext : DbContext +public class DocumentProcessorDbContext : DbContext { public DbSet Contacts => Set(); diff --git a/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs index 7e3ea1c..0d310c1 100644 --- a/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs +++ b/DocumentProcessor.yemiodetola/Migrations/20251208000159_pendingMigrations.Designer.cs @@ -8,7 +8,7 @@ namespace DocumentProcessor.yemiodetola.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(DocumentProcessorDbContext))] [Migration("20251208000159_pendingMigrations")] partial class pendingMigrations { diff --git a/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.Designer.cs b/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.Designer.cs new file mode 100644 index 0000000..ba01e46 --- /dev/null +++ b/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.Designer.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace DocumentProcessor.yemiodetola.Migrations +{ + [DbContext(typeof(DocumentProcessorDbContext))] + [Migration("20251208214440_contextupdate")] + partial class contextupdate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.0"); + + modelBuilder.Entity("DocumentProcessor.yemiodetola.Models.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.cs b/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.cs new file mode 100644 index 0000000..c2220a8 --- /dev/null +++ b/DocumentProcessor.yemiodetola/Migrations/20251208214440_contextupdate.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace DocumentProcessor.yemiodetola.Migrations +{ + /// + public partial class contextupdate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs b/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs index 0a73941..91a7e85 100644 --- a/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs +++ b/DocumentProcessor.yemiodetola/Migrations/AppDbContextModelSnapshot.cs @@ -7,7 +7,7 @@ namespace DocumentProcessor.yemiodetola.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(DocumentProcessorDbContext))] partial class AppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) diff --git a/DocumentProcessor.yemiodetola/Models/Contact.cs b/DocumentProcessor.yemiodetola/Models/Contact.cs index 6e29f2b..983564b 100644 --- a/DocumentProcessor.yemiodetola/Models/Contact.cs +++ b/DocumentProcessor.yemiodetola/Models/Contact.cs @@ -7,7 +7,7 @@ public class Contact public string Email { get; set; } = string.Empty; public string PhoneNumber { get; set; } = string.Empty; - public bool validateEmailAddress(string email) + public bool ValidateEmailAddress(string email) { if (String.IsNullOrEmpty(email)) { @@ -21,7 +21,7 @@ public bool validateEmailAddress(string email) && dotIndex < email.Length - 1; } - public bool validatePhoneNumber(string phoneNumber) + public bool ValidatePhoneNumber(string phoneNumber) { return !String.IsNullOrEmpty(phoneNumber) && phoneNumber.Length == 11 diff --git a/DocumentProcessor.yemiodetola/Program.cs b/DocumentProcessor.yemiodetola/Program.cs index 0730c39..085d5bd 100644 --- a/DocumentProcessor.yemiodetola/Program.cs +++ b/DocumentProcessor.yemiodetola/Program.cs @@ -11,13 +11,11 @@ static async Task Main(string[] args) var services = new ServiceCollection(); - services.AddSingleton(); - services.AddSingleton(); - + services.AddSingleton(); + services.AddSingleton(); var provider = services.BuildServiceProvider(); - - var excelService = provider.GetRequiredService(); - var pdfService = provider.GetRequiredService(); + var excelService = provider.GetRequiredService(); + var pdfService = provider.GetRequiredService(); var excelPath = AppContext.GetData("ExcelFilePath") as string ?? "./sample.xlsx"; var rows = await excelService.ReadExcelAsync(excelPath); @@ -29,7 +27,7 @@ static async Task Main(string[] args) } Console.WriteLine("\nExcel File Contents:\n"); - Console.WriteLine($"{ "Name",-25} { "Email",-35} { "Phone Number",-15}"); + Console.WriteLine($"{"Name",-25} {"Email",-35} {"Phone Number",-15}"); Console.WriteLine(new string('-', 80)); foreach (var contact in rows) { diff --git a/DocumentProcessor.yemiodetola/README.md b/DocumentProcessor.yemiodetola/README.md index b286eae..3aa0710 100644 --- a/DocumentProcessor.yemiodetola/README.md +++ b/DocumentProcessor.yemiodetola/README.md @@ -8,7 +8,7 @@ ``` 2. **Add your Excel file** - - Place your Excel file (e.g., `sample.xlsx`) in the project directory (There's a sample currently). + - Place your Excel file (e.g., `sample.xlsx`) in the project directory. A sample file (`sample.xlsx`) is already provided for testing. - Ensure the worksheet is named `Contacts` and has columns: Name, Email, Phone Number. 3. **Run the program** diff --git a/DocumentProcessor.yemiodetola/Services/ContactsDocument.cs b/DocumentProcessor.yemiodetola/Services/ContactsDocument.cs new file mode 100644 index 0000000..79dcc0d --- /dev/null +++ b/DocumentProcessor.yemiodetola/Services/ContactsDocument.cs @@ -0,0 +1,53 @@ +using QuestPDF.Fluent; +using QuestPDF.Infrastructure; +using DocumentProcessor.yemiodetola.Models; + +namespace DocumentProcessor.yemiodetola.Services; + +public class ContactsDocument : IDocument +{ + private readonly List _contacts; + public ContactsDocument(List contacts) + { + _contacts = contacts; + } + + public DocumentMetadata GetMetadata() => new DocumentMetadata(); + public DocumentSettings GetSettings() => new DocumentSettings(); + + public void Compose(IDocumentContainer container) + { + container.Page(page => + { + page.Margin(30); + + page.Header().Text("Contact List") + .FontSize(20) + .SemiBold() + .AlignCenter(); + + page.Content().Table(table => + { + table.ColumnsDefinition(columns => + { + columns.ConstantColumn(150); + columns.RelativeColumn(); + columns.ConstantColumn(120); + }); + table.Header(header => + { + header.Cell().Text("Name").SemiBold(); + header.Cell().Text("Email").SemiBold(); + header.Cell().Text("Phone Number").SemiBold(); + }); + foreach (var c in _contacts) + { + table.Cell().Text(c.Name); + table.Cell().Text(c.Email); + table.Cell().Text(c.PhoneNumber); + } + }); + }); + } +} + diff --git a/DocumentProcessor.yemiodetola/Services/PdfService.cs b/DocumentProcessor.yemiodetola/Services/PdfService.cs index 79dcc0d..573fe7a 100644 --- a/DocumentProcessor.yemiodetola/Services/PdfService.cs +++ b/DocumentProcessor.yemiodetola/Services/PdfService.cs @@ -1,53 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; using QuestPDF.Fluent; -using QuestPDF.Infrastructure; using DocumentProcessor.yemiodetola.Models; - namespace DocumentProcessor.yemiodetola.Services; - -public class ContactsDocument : IDocument +public class PdfService : IPdfService { - private readonly List _contacts; - public ContactsDocument(List contacts) - { - _contacts = contacts; - } - - public DocumentMetadata GetMetadata() => new DocumentMetadata(); - public DocumentSettings GetSettings() => new DocumentSettings(); - - public void Compose(IDocumentContainer container) - { - container.Page(page => + public async Task GeneratePdfAsync(string outputPath, List contacts) { - page.Margin(30); - - page.Header().Text("Contact List") - .FontSize(20) - .SemiBold() - .AlignCenter(); - - page.Content().Table(table => - { - table.ColumnsDefinition(columns => - { - columns.ConstantColumn(150); - columns.RelativeColumn(); - columns.ConstantColumn(120); - }); - table.Header(header => - { - header.Cell().Text("Name").SemiBold(); - header.Cell().Text("Email").SemiBold(); - header.Cell().Text("Phone Number").SemiBold(); - }); - foreach (var c in _contacts) - { - table.Cell().Text(c.Name); - table.Cell().Text(c.Email); - table.Cell().Text(c.PhoneNumber); - } - }); - }); - } + await Task.Run(() => + { + var document = new ContactsDocument(contacts); + document.GeneratePdf(outputPath); + }); + } } - diff --git a/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs b/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs deleted file mode 100644 index 573fe7a..0000000 --- a/DocumentProcessor.yemiodetola/Services/PdfServiceFixed.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using QuestPDF.Fluent; -using DocumentProcessor.yemiodetola.Models; -namespace DocumentProcessor.yemiodetola.Services; -public class PdfService : IPdfService -{ - public async Task GeneratePdfAsync(string outputPath, List contacts) - { - await Task.Run(() => - { - var document = new ContactsDocument(contacts); - document.GeneratePdf(outputPath); - }); - } -} diff --git a/DocumentProcessor.yemiodetola/excel_data.db b/DocumentProcessor.yemiodetola/excel_data.db index 7e33126105022c6aeba6c8d03d96f5d8acb245d4..d9f7767779411266ecf92ac05b5c736e3d5f679d 100644 GIT binary patch delta 163 zcmZoTz}Rqrae_1>=R_H2M$U~1-SUb?hE^sfR;Gq}#-Z#2`L7Kd&UUqNKDSC9x#cQqMroK!cIl qSA6mVdxgor^*K41_;)k#-{Id4G+{lzq5`ukBR&I|Hs7&VQ2+o^cPteE delta 87 zcmZoTz}Rqrae_1>+e8^>Mz)O!-SYAV237_}Rt6?|hGyo*W|rC@z5y=-0|O)fH3t4` pn*{^T@Uv<#GW&{8eqgUK`L{kN8zcW62L3xhnM?ee@7Sv-007~V7z+RZ