From 32ae79524e719739216691eab92140d6ea4d4147 Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 15 Jun 2025 14:51:03 +0530 Subject: [PATCH 01/25] refactor: convert to file-scoped namespaces --- DocumentService/Pdf/Models/ContentMetaData.cs | 19 +- DocumentService/Pdf/Models/DocumentData.cs | 21 +- DocumentService/Pdf/PdfDocumentGenerator.cs | 425 ++++++------ DocumentService/Word/Models/ContentData.cs | 59 +- DocumentService/Word/Models/DocumentData.cs | 47 +- DocumentService/Word/Models/Enums.cs | 71 +- DocumentService/Word/Models/TableData.cs | 45 +- DocumentService/Word/WordDocumentGenerator.cs | 617 +++++++++--------- 8 files changed, 648 insertions(+), 656 deletions(-) diff --git a/DocumentService/Pdf/Models/ContentMetaData.cs b/DocumentService/Pdf/Models/ContentMetaData.cs index 1f9cb3e..fd8d22e 100644 --- a/DocumentService/Pdf/Models/ContentMetaData.cs +++ b/DocumentService/Pdf/Models/ContentMetaData.cs @@ -1,10 +1,9 @@ -namespace DocumentService.Pdf.Models -{ - public class ContentMetaData - { - public string Placeholder { get; set; } - public string Content { get; set; } - } -} - - +namespace DocumentService.Pdf.Models; + +public class ContentMetaData +{ + public string Placeholder { get; set; } + public string Content { get; set; } +} + + diff --git a/DocumentService/Pdf/Models/DocumentData.cs b/DocumentService/Pdf/Models/DocumentData.cs index 9a93c7a..3427bca 100644 --- a/DocumentService/Pdf/Models/DocumentData.cs +++ b/DocumentService/Pdf/Models/DocumentData.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace DocumentService.Pdf.Models -{ - public class DocumentData - { - public List Placeholders { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Text; + +namespace DocumentService.Pdf.Models; + +public class DocumentData +{ + public List Placeholders { get; set; } +} diff --git a/DocumentService/Pdf/PdfDocumentGenerator.cs b/DocumentService/Pdf/PdfDocumentGenerator.cs index a3cb308..d293c4b 100644 --- a/DocumentService/Pdf/PdfDocumentGenerator.cs +++ b/DocumentService/Pdf/PdfDocumentGenerator.cs @@ -1,213 +1,212 @@ -using DocumentService.Pdf.Models; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -namespace DocumentService.Pdf -{ - public class PdfDocumentGenerator - { - public static void GeneratePdf(string toolFolderAbsolutePath, string templatePath, List metaDataList, string outputFilePath, bool isEjsTemplate, string serializedEjsDataJson) - { - try - { - if (!File.Exists(templatePath)) - { - throw new Exception("The file path you provided is not valid."); - } - - if (isEjsTemplate) - { - // Validate if template in file path is an ejs file - if (Path.GetExtension(templatePath).ToLower() != ".ejs") - { - throw new Exception("Input template should be a valid EJS file"); - } - - // Convert ejs file to an equivalent html - templatePath = ConvertEjsToHTML(templatePath, outputFilePath, serializedEjsDataJson); - } - - // Modify html template with content data and generate pdf - string modifiedHtmlFilePath = ReplaceFileElementsWithMetaData(templatePath, metaDataList, outputFilePath); - ConvertHtmlToPdf(toolFolderAbsolutePath, modifiedHtmlFilePath, outputFilePath); - - if (isEjsTemplate) - { - // If input template was an ejs file, then the template path contains path to html converted from ejs - if (Path.GetExtension(templatePath).ToLower() == ".html") - { - // If template path contains path to converted html template then delete it - File.Delete(templatePath); - } - } - } - catch (Exception e) - { - throw e; - } - } - - private static string ReplaceFileElementsWithMetaData(string templatePath, List metaDataList, string outputFilePath) - { - string htmlContent = File.ReadAllText(templatePath); - - foreach (ContentMetaData metaData in metaDataList) - { - htmlContent = htmlContent.Replace($"{{{metaData.Placeholder}}}", metaData.Content); - } - - string directoryPath = Path.GetDirectoryName(outputFilePath); - string tempHtmlFilePath = Path.Combine(directoryPath, "Modified"); - string tempHtmlFile = Path.Combine(tempHtmlFilePath, "modifiedHtml.html"); - - if (!Directory.Exists(tempHtmlFilePath)) - { - Directory.CreateDirectory(tempHtmlFilePath); - } - - File.WriteAllText(tempHtmlFile, htmlContent); - return tempHtmlFile; - } - - private static void ConvertHtmlToPdf(string toolFolderAbsolutePath, string modifiedHtmlFilePath, string outputFilePath) - { - string wkHtmlToPdfPath = "cmd.exe"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - wkHtmlToPdfPath = "wkhtmltopdf"; - } - - /* - * FIXME: Issue if tools file path has spaces in between - */ - string arguments = HtmlToPdfArgumentsBasedOnOS(toolFolderAbsolutePath, modifiedHtmlFilePath, outputFilePath); - - ProcessStartInfo psi = new ProcessStartInfo - { - FileName = wkHtmlToPdfPath, - Arguments = arguments, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using (Process process = new Process()) - { - process.StartInfo = psi; - process.Start(); - process.WaitForExit(); - string output = process.StandardOutput.ReadToEnd(); - string errors = process.StandardError.ReadToEnd(); - } - - File.Delete(modifiedHtmlFilePath); - } - - private static string ConvertEjsToHTML(string ejsFilePath, string outputFilePath, string ejsDataJson) - { - // Generate directory - string directoryPath = Path.GetDirectoryName(outputFilePath); - string tempDirectoryFilePath = Path.Combine(directoryPath, "Temp"); - - if (!Directory.Exists(tempDirectoryFilePath)) - { - Directory.CreateDirectory(tempDirectoryFilePath); - } - - // Generate file path to converted html template - string tempHtmlFilePath = Path.Combine(tempDirectoryFilePath, "htmlTemplate.html"); - - // If the ejs data json is invalid then throw exception - if (!string.IsNullOrWhiteSpace(ejsDataJson) && !IsValidJSON(ejsDataJson)) - { - throw new Exception("Received invalid JSON data for EJS template"); - } - - // Write json data string to json file - string ejsDataJsonFilePath = Path.Combine(tempDirectoryFilePath, "ejsData.json"); - File.WriteAllText(ejsDataJsonFilePath, ejsDataJson); - - string commandLine = "cmd.exe"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - commandLine = "ejs"; - } - string arguments = EjsToHtmlArgumentsBasedOnOS(ejsFilePath, ejsDataJsonFilePath, tempHtmlFilePath); - - ProcessStartInfo psi = new ProcessStartInfo - { - FileName = commandLine, - Arguments = arguments, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - - using (Process process = new Process()) - { - process.StartInfo = psi; - process.Start(); - process.WaitForExit(); - string output = process.StandardOutput.ReadToEnd(); - string errors = process.StandardError.ReadToEnd(); - } - - // Delete json data file - File.Delete(ejsDataJsonFilePath); - - return tempHtmlFilePath; - } - - private static bool IsValidJSON(string json) - { - try - { - JToken.Parse(json); - return true; - } - catch (JsonReaderException) - { - return false; - } - } - - private static string HtmlToPdfArgumentsBasedOnOS(string toolFolderAbsolutePath, string modifiedHtmlFilePath, string outputFilePath) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return $"/C {toolFolderAbsolutePath} \"{modifiedHtmlFilePath}\" \"{outputFilePath}\""; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return $"{modifiedHtmlFilePath} {outputFilePath}"; - } - else - { - throw new Exception("Unknown operating system"); - } - } - - private static string EjsToHtmlArgumentsBasedOnOS(string ejsFilePath, string ejsDataJsonFilePath, string tempHtmlFilePath) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return $"/C ejs \"{ejsFilePath}\" -f \"{ejsDataJsonFilePath}\" -o \"{tempHtmlFilePath}\""; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return $"{ejsFilePath} -f {ejsDataJsonFilePath} -o {tempHtmlFilePath}"; - } - else - { - throw new Exception("Unknown operating system"); - } - } - } -} +using DocumentService.Pdf.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace DocumentService.Pdf; + +public class PdfDocumentGenerator +{ + public static void GeneratePdf(string toolFolderAbsolutePath, string templatePath, List metaDataList, string outputFilePath, bool isEjsTemplate, string serializedEjsDataJson) + { + try + { + if (!File.Exists(templatePath)) + { + throw new Exception("The file path you provided is not valid."); + } + + if (isEjsTemplate) + { + // Validate if template in file path is an ejs file + if (Path.GetExtension(templatePath).ToLower() != ".ejs") + { + throw new Exception("Input template should be a valid EJS file"); + } + + // Convert ejs file to an equivalent html + templatePath = ConvertEjsToHTML(templatePath, outputFilePath, serializedEjsDataJson); + } + + // Modify html template with content data and generate pdf + string modifiedHtmlFilePath = ReplaceFileElementsWithMetaData(templatePath, metaDataList, outputFilePath); + ConvertHtmlToPdf(toolFolderAbsolutePath, modifiedHtmlFilePath, outputFilePath); + + if (isEjsTemplate) + { + // If input template was an ejs file, then the template path contains path to html converted from ejs + if (Path.GetExtension(templatePath).ToLower() == ".html") + { + // If template path contains path to converted html template then delete it + File.Delete(templatePath); + } + } + } + catch (Exception e) + { + throw; + } + } + + private static string ReplaceFileElementsWithMetaData(string templatePath, List metaDataList, string outputFilePath) + { + string htmlContent = File.ReadAllText(templatePath); + + foreach (ContentMetaData metaData in metaDataList) + { + htmlContent = htmlContent.Replace($"{{{metaData.Placeholder}}}", metaData.Content); + } + + string directoryPath = Path.GetDirectoryName(outputFilePath); + string tempHtmlFilePath = Path.Combine(directoryPath, "Modified"); + string tempHtmlFile = Path.Combine(tempHtmlFilePath, "modifiedHtml.html"); + + if (!Directory.Exists(tempHtmlFilePath)) + { + Directory.CreateDirectory(tempHtmlFilePath); + } + + File.WriteAllText(tempHtmlFile, htmlContent); + return tempHtmlFile; + } + + private static void ConvertHtmlToPdf(string toolFolderAbsolutePath, string modifiedHtmlFilePath, string outputFilePath) + { + string wkHtmlToPdfPath = "cmd.exe"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + wkHtmlToPdfPath = "wkhtmltopdf"; + } + + /* + * FIXME: Issue if tools file path has spaces in between + */ + string arguments = HtmlToPdfArgumentsBasedOnOS(toolFolderAbsolutePath, modifiedHtmlFilePath, outputFilePath); + + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = wkHtmlToPdfPath, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (Process process = new Process()) + { + process.StartInfo = psi; + process.Start(); + process.WaitForExit(); + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + } + + File.Delete(modifiedHtmlFilePath); + } + + private static string ConvertEjsToHTML(string ejsFilePath, string outputFilePath, string ejsDataJson) + { + // Generate directory + string directoryPath = Path.GetDirectoryName(outputFilePath); + string tempDirectoryFilePath = Path.Combine(directoryPath, "Temp"); + + if (!Directory.Exists(tempDirectoryFilePath)) + { + Directory.CreateDirectory(tempDirectoryFilePath); + } + + // Generate file path to converted html template + string tempHtmlFilePath = Path.Combine(tempDirectoryFilePath, "htmlTemplate.html"); + + // If the ejs data json is invalid then throw exception + if (!string.IsNullOrWhiteSpace(ejsDataJson) && !IsValidJSON(ejsDataJson)) + { + throw new Exception("Received invalid JSON data for EJS template"); + } + + // Write json data string to json file + string ejsDataJsonFilePath = Path.Combine(tempDirectoryFilePath, "ejsData.json"); + File.WriteAllText(ejsDataJsonFilePath, ejsDataJson); + + string commandLine = "cmd.exe"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + commandLine = "ejs"; + } + string arguments = EjsToHtmlArgumentsBasedOnOS(ejsFilePath, ejsDataJsonFilePath, tempHtmlFilePath); + + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = commandLine, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (Process process = new Process()) + { + process.StartInfo = psi; + process.Start(); + process.WaitForExit(); + string output = process.StandardOutput.ReadToEnd(); + string errors = process.StandardError.ReadToEnd(); + } + + // Delete json data file + File.Delete(ejsDataJsonFilePath); + + return tempHtmlFilePath; + } + + private static bool IsValidJSON(string json) + { + try + { + JToken.Parse(json); + return true; + } + catch (JsonReaderException) + { + return false; + } + } + + private static string HtmlToPdfArgumentsBasedOnOS(string toolFolderAbsolutePath, string modifiedHtmlFilePath, string outputFilePath) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $"/C {toolFolderAbsolutePath} \"{modifiedHtmlFilePath}\" \"{outputFilePath}\""; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return $"{modifiedHtmlFilePath} {outputFilePath}"; + } + else + { + throw new Exception("Unknown operating system"); + } + } + + private static string EjsToHtmlArgumentsBasedOnOS(string ejsFilePath, string ejsDataJsonFilePath, string tempHtmlFilePath) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return $"/C ejs \"{ejsFilePath}\" -f \"{ejsDataJsonFilePath}\" -o \"{tempHtmlFilePath}\""; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return $"{ejsFilePath} -f {ejsDataJsonFilePath} -o {tempHtmlFilePath}"; + } + else + { + throw new Exception("Unknown operating system"); + } + } +} diff --git a/DocumentService/Word/Models/ContentData.cs b/DocumentService/Word/Models/ContentData.cs index c6f4ae2..38391ba 100644 --- a/DocumentService/Word/Models/ContentData.cs +++ b/DocumentService/Word/Models/ContentData.cs @@ -1,30 +1,29 @@ -namespace DocumentService.Word.Models -{ - - /// - /// Represents the data for a content placeholder in a Word document. - /// - public class ContentData - { - /// - /// Gets or sets the placeholder name. - /// - public string Placeholder { get; set; } - - /// - /// Gets or sets the content to replace the placeholder with. - /// - public string Content { get; set; } - - /// - /// Gets or sets the content type of the placeholder (text or image). - /// - public ContentType ContentType { get; set; } - - /// - /// Gets or sets the parent body of the placeholder (none or table). - /// - - public ParentBody ParentBody { get; set; } - } -} +namespace DocumentService.Word.Models; + + +/// +/// Represents the data for a content placeholder in a Word document. +/// +public class ContentData +{ + /// + /// Gets or sets the placeholder name. + /// + public string Placeholder { get; set; } + + /// + /// Gets or sets the content to replace the placeholder with. + /// + public string Content { get; set; } + + /// + /// Gets or sets the content type of the placeholder (text or image). + /// + public ContentType ContentType { get; set; } + + /// + /// Gets or sets the parent body of the placeholder (none or table). + /// + + public ParentBody ParentBody { get; set; } +} diff --git a/DocumentService/Word/Models/DocumentData.cs b/DocumentService/Word/Models/DocumentData.cs index 1ca80fe..5b56a8e 100644 --- a/DocumentService/Word/Models/DocumentData.cs +++ b/DocumentService/Word/Models/DocumentData.cs @@ -1,24 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace DocumentService.Word.Models -{ - - /// - /// Represents the data for a Word document, including content placeholders and table data. - /// - public class DocumentData - { - /// - /// Gets or sets the list of content placeholders in the document. - /// - public List Placeholders { get; set; } - - /// - /// Gets or sets the list of table data in the document. - /// - - public List TablesData { get; set; } - } -} +using System; +using System.Collections.Generic; +using System.Text; + +namespace DocumentService.Word.Models; + + +/// +/// Represents the data for a Word document, including content placeholders and table data. +/// +public class DocumentData +{ + /// + /// Gets or sets the list of content placeholders in the document. + /// + public List Placeholders { get; set; } + + /// + /// Gets or sets the list of table data in the document. + /// + + public List TablesData { get; set; } +} diff --git a/DocumentService/Word/Models/Enums.cs b/DocumentService/Word/Models/Enums.cs index dff7e01..31fedc5 100644 --- a/DocumentService/Word/Models/Enums.cs +++ b/DocumentService/Word/Models/Enums.cs @@ -1,36 +1,35 @@ -namespace DocumentService.Word.Models -{ - - /// - /// Represents the content type of a placeholder in a Word document. - /// - public enum ContentType - { - /// - /// The placeholder represents text content. - /// - Text = 0, - - /// - /// The placeholder represents an image. - /// - Image = 1 - } - - /// - /// Represents the parent body of a placeholder in a Word document. - /// - public enum ParentBody - { - /// - /// The placeholder does not have a parent body. - /// - None = 0, - - /// - /// The placeholder belongs to a table. - /// - - Table = 1 - } -} +namespace DocumentService.Word.Models; + + +/// +/// Represents the content type of a placeholder in a Word document. +/// +public enum ContentType +{ + /// + /// The placeholder represents text content. + /// + Text = 0, + + /// + /// The placeholder represents an image. + /// + Image = 1 +} + +/// +/// Represents the parent body of a placeholder in a Word document. +/// +public enum ParentBody +{ + /// + /// The placeholder does not have a parent body. + /// + None = 0, + + /// + /// The placeholder belongs to a table. + /// + + Table = 1 +} diff --git a/DocumentService/Word/Models/TableData.cs b/DocumentService/Word/Models/TableData.cs index f5e8a2a..dba7a72 100644 --- a/DocumentService/Word/Models/TableData.cs +++ b/DocumentService/Word/Models/TableData.cs @@ -1,23 +1,22 @@ -using System.Collections.Generic; - -namespace DocumentService.Word.Models -{ - - /// - /// Represents the data for a table in a Word document. - /// - public class TableData - { - /// - /// Gets or sets the position of the table in the document. - /// - public int TablePos { get; set; } - - /// - /// Gets or sets the list of dictionaries representing the data for each row in the table. - /// Each dictionary contains column header-value pairs. - /// - - public List> Data { get; set; } - } -} +using System.Collections.Generic; + +namespace DocumentService.Word.Models; + + +/// +/// Represents the data for a table in a Word document. +/// +public class TableData +{ + /// + /// Gets or sets the position of the table in the document. + /// + public int TablePos { get; set; } + + /// + /// Gets or sets the list of dictionaries representing the data for each row in the table. + /// Each dictionary contains column header-value pairs. + /// + + public List> Data { get; set; } +} diff --git a/DocumentService/Word/WordDocumentGenerator.cs b/DocumentService/Word/WordDocumentGenerator.cs index 659d55e..83198f5 100644 --- a/DocumentService/Word/WordDocumentGenerator.cs +++ b/DocumentService/Word/WordDocumentGenerator.cs @@ -1,310 +1,309 @@ -using DocumentFormat.OpenXml.Drawing; -using DocumentFormat.OpenXml.Drawing.Wordprocessing; -using DocumentFormat.OpenXml.Packaging; -using DocumentFormat.OpenXml.Wordprocessing; -using DocumentService.Word.Models; -using NPOI.XWPF.UserModel; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text.RegularExpressions; - -namespace DocumentService.Word -{ - /// - /// Provides functionality to generate Word documents based on templates and data. - /// - public static class WordDocumentGenerator - { - /// - /// Generates a Word document based on a template, replaces placeholders with data, and saves it to the specified output file path. - /// - /// The file path of the template document. - /// The data to replace the placeholders in the template. - /// The file path to save the generated document. - public static void GenerateDocumentByTemplate(string templateFilePath, DocumentData documentData, string outputFilePath) - { - try - { - List contentData = documentData.Placeholders; - List tablesData = documentData.TablesData; - - // Creating dictionaries for each type of placeholders - Dictionary textPlaceholders = new Dictionary(); - Dictionary tableContentPlaceholders = new Dictionary(); - Dictionary imagePlaceholders = new Dictionary(); - - foreach (ContentData content in contentData) - { - if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Text) - { - string placeholder = "{" + content.Placeholder + "}"; - textPlaceholders.Add(placeholder, content.Content); - } - else if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Image) - { - string placeholder = content.Placeholder; - imagePlaceholders.Add(placeholder, content.Content); - } - else if (content.ParentBody == ParentBody.Table && content.ContentType == ContentType.Text) - { - string placeholder = "{" + content.Placeholder + "}"; - tableContentPlaceholders.Add(placeholder, content.Content); - } - } - - // Create document of the template - XWPFDocument document = GetXWPFDocument(templateFilePath); - - // For each element in the document - foreach (IBodyElement element in document.BodyElements) - { - if (element.ElementType == BodyElementType.PARAGRAPH) - { - // If element is a paragraph - XWPFParagraph paragraph = (XWPFParagraph)element; - - // If the paragraph is empty string or the placeholder regex does not match then continue - if (paragraph.ParagraphText == string.Empty || !new Regex(@"{[a-zA-Z]+}").IsMatch(paragraph.ParagraphText)) - { - continue; - } - - // Replace placeholders in paragraph with values - paragraph = ReplacePlaceholdersOnBody(paragraph, textPlaceholders); - } - else if (element.ElementType == BodyElementType.TABLE) - { - // If element is a table - XWPFTable table = (XWPFTable)element; - - // Replace placeholders in a table - table = ReplacePlaceholderOnTables(table, tableContentPlaceholders); - - // Populate the table with data if it is passed in tablesData list - foreach (TableData insertData in tablesData) - { - if (insertData.TablePos <= document.Tables.Count && table == document.Tables[insertData.TablePos - 1]) - { - table = PopulateTable(table, insertData); - } - } - } - } - - // Write the document to output file path and close the document - WriteDocument(document, outputFilePath); - document.Close(); - - /* - * Image Replacement is done after writing the document here, - * because for Text Replacement, NPOI package is being used - * and for Image Replacement, OpeXML package is used. - * Since both the packages have different execution method, so they are handled separately - */ - // Replace all the image placeholders in the output file - ReplaceImagePlaceholders(outputFilePath, outputFilePath, imagePlaceholders); - } - catch (Exception ex) - { - throw ex; - } - } - - /// - /// Retrieves an instance of XWPFDocument from the specified document file path. - /// - /// The file path of the Word document. - /// An instance of XWPFDocument representing the Word document. - private static XWPFDocument GetXWPFDocument(string docFilePath) - { - FileStream readStream = File.OpenRead(docFilePath); - XWPFDocument document = new XWPFDocument(readStream); - readStream.Close(); - return document; - } - - /// - /// Writes the XWPFDocument to the specified file path. - /// - /// The XWPFDocument to write. - /// The file path to save the document. - private static void WriteDocument(XWPFDocument document, string filePath) - { - using (FileStream writeStream = File.Create(filePath)) - { - document.Write(writeStream); - } - } - - /// - /// Replaces the text placeholders in a paragraph with the specified values. - /// - /// The XWPFParagraph containing the placeholders. - /// The dictionary of text placeholders and their corresponding values. - /// The updated XWPFParagraph. - private static XWPFParagraph ReplacePlaceholdersOnBody(XWPFParagraph paragraph, Dictionary textPlaceholders) - { - // Get a list of all placeholders in the current paragraph - List placeholdersTobeReplaced = Regex.Matches(paragraph.ParagraphText, @"{[a-zA-Z]+}") - .Cast() - .Select(s => s.Groups[0].Value).ToList(); - - // For each placeholder in paragraph - foreach (string placeholder in placeholdersTobeReplaced) - { - // Replace text placeholders in paragraph with values - if (textPlaceholders.ContainsKey(placeholder)) - { - paragraph.ReplaceText(placeholder, textPlaceholders[placeholder]); - } - - paragraph.SpacingAfter = 0; - } - - return paragraph; - } - - /// - /// Replaces the text placeholders in a table with the specified values. - /// - /// The XWPFTable containing the placeholders. - /// The dictionary of table content placeholders and their corresponding values. - /// The updated XWPFTable. - private static XWPFTable ReplacePlaceholderOnTables(XWPFTable table, Dictionary tableContentPlaceholders) - { - // Loop through each cell of the table - foreach (XWPFTableRow row in table.Rows) - { - foreach (XWPFTableCell cell in row.GetTableCells()) - { - foreach (XWPFParagraph paragraph in cell.Paragraphs) - { - // Get a list of all placeholders in the current cell - List placeholdersTobeReplaced = Regex.Matches(paragraph.ParagraphText, @"{[a-zA-Z]+}") - .Cast() - .Select(s => s.Groups[0].Value).ToList(); - - // For each placeholder in the cell - foreach (string placeholder in placeholdersTobeReplaced) - { - // replace the placeholder with its value - if (tableContentPlaceholders.ContainsKey(placeholder)) - { - paragraph.ReplaceText(placeholder, tableContentPlaceholders[placeholder]); - } - } - } - } - } - - return table; - } - - /// - /// Populates a table with the specified data. - /// - /// The XWPFTable to populate. - /// The data to populate the table. - /// The updated XWPFTable. - private static XWPFTable PopulateTable(XWPFTable table, TableData tableData) - { - // Get the header row - XWPFTableRow headerRow = table.GetRow(0); - - // Return if no header row found or if it does not have any column - if (headerRow == null || headerRow.GetTableCells() == null || headerRow.GetTableCells().Count <= 0) - { - return table; - } - - // For each row's data stored in table data - foreach (Dictionary rowData in tableData.Data) - { - // Create a new row and its columns - XWPFTableRow row = table.CreateRow(); - - // For each cell in row - for (int cellNumber = 0; cellNumber < row.GetTableCells().Count; cellNumber++) - { - XWPFTableCell cell = row.GetCell(cellNumber); - - // Get the column header of this cell - string columnHeader = headerRow.GetCell(cellNumber).GetText(); - - // Add the cell's value - if (rowData.ContainsKey(columnHeader)) - { - cell.SetText(rowData[columnHeader]); - } - } - } - - return table; - } - - /// - /// Replaces the image placeholders in the output file with the specified images. - /// - /// The input file path containing the image placeholders. - /// The output file path where the updated document will be saved. - /// The dictionary of image placeholders and their corresponding image paths. - private static void ReplaceImagePlaceholders(string inputFilePath, string outputFilePath, Dictionary imagePlaceholders) - { - byte[] docBytes = File.ReadAllBytes(inputFilePath); - - // Write document bytes to memory - MemoryStream memoryStream = new MemoryStream(); - memoryStream.Write(docBytes, 0, docBytes.Length); - - using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(memoryStream, true)) - { - MainDocumentPart mainDocumentPart = wordDocument.MainDocumentPart; - - // Get a list of drawings (images) - IEnumerable drawings = mainDocumentPart.Document.Descendants().ToList(); - - /* - * FIXME: Look on how we can improve this loop operation. - */ - foreach (Drawing drawing in drawings) - { - DocProperties docProperty = drawing.Descendants().FirstOrDefault(); - - // If drawing / image name is present in imagePlaceholders dictionary, then replace image - if (docProperty != null && imagePlaceholders.ContainsKey(docProperty.Name)) - { - List drawingBlips = drawing.Descendants().ToList(); - - foreach (Blip blip in drawingBlips) - { - OpenXmlPart imagePart = wordDocument.MainDocumentPart.GetPartById(blip.Embed); - - using (BinaryWriter writer = new BinaryWriter(imagePart.GetStream())) - { - string imagePath = imagePlaceholders[docProperty.Name]; - - /* - * WebClient has been deprecated and we need to use HTTPClient. - * This involves the methods to be asynchronous. - */ - using (WebClient webClient = new WebClient()) - { - writer.Write(webClient.DownloadData(imagePath)); - } - } - } - } - } - } - - // Overwrite the output file - FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); - memoryStream.WriteTo(fileStream); - fileStream.Close(); - memoryStream.Close(); - } - } +using DocumentFormat.OpenXml.Drawing; +using DocumentFormat.OpenXml.Drawing.Wordprocessing; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using DocumentService.Word.Models; +using NPOI.XWPF.UserModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; + +namespace DocumentService.Word; + +/// +/// Provides functionality to generate Word documents based on templates and data. +/// +public static class WordDocumentGenerator +{ + /// + /// Generates a Word document based on a template, replaces placeholders with data, and saves it to the specified output file path. + /// + /// The file path of the template document. + /// The data to replace the placeholders in the template. + /// The file path to save the generated document. + public static void GenerateDocumentByTemplate(string templateFilePath, DocumentData documentData, string outputFilePath) + { + try + { + List contentData = documentData.Placeholders; + List tablesData = documentData.TablesData; + + // Creating dictionaries for each type of placeholders + Dictionary textPlaceholders = new Dictionary(); + Dictionary tableContentPlaceholders = new Dictionary(); + Dictionary imagePlaceholders = new Dictionary(); + + foreach (ContentData content in contentData) + { + if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Text) + { + string placeholder = "{" + content.Placeholder + "}"; + textPlaceholders.Add(placeholder, content.Content); + } + else if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Image) + { + string placeholder = content.Placeholder; + imagePlaceholders.Add(placeholder, content.Content); + } + else if (content.ParentBody == ParentBody.Table && content.ContentType == ContentType.Text) + { + string placeholder = "{" + content.Placeholder + "}"; + tableContentPlaceholders.Add(placeholder, content.Content); + } + } + + // Create document of the template + XWPFDocument document = GetXWPFDocument(templateFilePath); + + // For each element in the document + foreach (IBodyElement element in document.BodyElements) + { + if (element.ElementType == BodyElementType.PARAGRAPH) + { + // If element is a paragraph + XWPFParagraph paragraph = (XWPFParagraph)element; + + // If the paragraph is empty string or the placeholder regex does not match then continue + if (paragraph.ParagraphText == string.Empty || !new Regex(@"{[a-zA-Z]+}").IsMatch(paragraph.ParagraphText)) + { + continue; + } + + // Replace placeholders in paragraph with values + paragraph = ReplacePlaceholdersOnBody(paragraph, textPlaceholders); + } + else if (element.ElementType == BodyElementType.TABLE) + { + // If element is a table + XWPFTable table = (XWPFTable)element; + + // Replace placeholders in a table + table = ReplacePlaceholderOnTables(table, tableContentPlaceholders); + + // Populate the table with data if it is passed in tablesData list + foreach (TableData insertData in tablesData) + { + if (insertData.TablePos <= document.Tables.Count && table == document.Tables[insertData.TablePos - 1]) + { + table = PopulateTable(table, insertData); + } + } + } + } + + // Write the document to output file path and close the document + WriteDocument(document, outputFilePath); + document.Close(); + + /* + * Image Replacement is done after writing the document here, + * because for Text Replacement, NPOI package is being used + * and for Image Replacement, OpeXML package is used. + * Since both the packages have different execution method, so they are handled separately + */ + // Replace all the image placeholders in the output file + ReplaceImagePlaceholders(outputFilePath, outputFilePath, imagePlaceholders); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Retrieves an instance of XWPFDocument from the specified document file path. + /// + /// The file path of the Word document. + /// An instance of XWPFDocument representing the Word document. + private static XWPFDocument GetXWPFDocument(string docFilePath) + { + FileStream readStream = File.OpenRead(docFilePath); + XWPFDocument document = new XWPFDocument(readStream); + readStream.Close(); + return document; + } + + /// + /// Writes the XWPFDocument to the specified file path. + /// + /// The XWPFDocument to write. + /// The file path to save the document. + private static void WriteDocument(XWPFDocument document, string filePath) + { + using (FileStream writeStream = File.Create(filePath)) + { + document.Write(writeStream); + } + } + + /// + /// Replaces the text placeholders in a paragraph with the specified values. + /// + /// The XWPFParagraph containing the placeholders. + /// The dictionary of text placeholders and their corresponding values. + /// The updated XWPFParagraph. + private static XWPFParagraph ReplacePlaceholdersOnBody(XWPFParagraph paragraph, Dictionary textPlaceholders) + { + // Get a list of all placeholders in the current paragraph + List placeholdersTobeReplaced = Regex.Matches(paragraph.ParagraphText, @"{[a-zA-Z]+}") + .Cast() + .Select(s => s.Groups[0].Value).ToList(); + + // For each placeholder in paragraph + foreach (string placeholder in placeholdersTobeReplaced) + { + // Replace text placeholders in paragraph with values + if (textPlaceholders.ContainsKey(placeholder)) + { + paragraph.ReplaceText(placeholder, textPlaceholders[placeholder]); + } + + paragraph.SpacingAfter = 0; + } + + return paragraph; + } + + /// + /// Replaces the text placeholders in a table with the specified values. + /// + /// The XWPFTable containing the placeholders. + /// The dictionary of table content placeholders and their corresponding values. + /// The updated XWPFTable. + private static XWPFTable ReplacePlaceholderOnTables(XWPFTable table, Dictionary tableContentPlaceholders) + { + // Loop through each cell of the table + foreach (XWPFTableRow row in table.Rows) + { + foreach (XWPFTableCell cell in row.GetTableCells()) + { + foreach (XWPFParagraph paragraph in cell.Paragraphs) + { + // Get a list of all placeholders in the current cell + List placeholdersTobeReplaced = Regex.Matches(paragraph.ParagraphText, @"{[a-zA-Z]+}") + .Cast() + .Select(s => s.Groups[0].Value).ToList(); + + // For each placeholder in the cell + foreach (string placeholder in placeholdersTobeReplaced) + { + // replace the placeholder with its value + if (tableContentPlaceholders.ContainsKey(placeholder)) + { + paragraph.ReplaceText(placeholder, tableContentPlaceholders[placeholder]); + } + } + } + } + } + + return table; + } + + /// + /// Populates a table with the specified data. + /// + /// The XWPFTable to populate. + /// The data to populate the table. + /// The updated XWPFTable. + private static XWPFTable PopulateTable(XWPFTable table, TableData tableData) + { + // Get the header row + XWPFTableRow headerRow = table.GetRow(0); + + // Return if no header row found or if it does not have any column + if (headerRow == null || headerRow.GetTableCells() == null || headerRow.GetTableCells().Count <= 0) + { + return table; + } + + // For each row's data stored in table data + foreach (Dictionary rowData in tableData.Data) + { + // Create a new row and its columns + XWPFTableRow row = table.CreateRow(); + + // For each cell in row + for (int cellNumber = 0; cellNumber < row.GetTableCells().Count; cellNumber++) + { + XWPFTableCell cell = row.GetCell(cellNumber); + + // Get the column header of this cell + string columnHeader = headerRow.GetCell(cellNumber).GetText(); + + // Add the cell's value + if (rowData.ContainsKey(columnHeader)) + { + cell.SetText(rowData[columnHeader]); + } + } + } + + return table; + } + + /// + /// Replaces the image placeholders in the output file with the specified images. + /// + /// The input file path containing the image placeholders. + /// The output file path where the updated document will be saved. + /// The dictionary of image placeholders and their corresponding image paths. + private static void ReplaceImagePlaceholders(string inputFilePath, string outputFilePath, Dictionary imagePlaceholders) + { + byte[] docBytes = File.ReadAllBytes(inputFilePath); + + // Write document bytes to memory + MemoryStream memoryStream = new MemoryStream(); + memoryStream.Write(docBytes, 0, docBytes.Length); + + using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(memoryStream, true)) + { + MainDocumentPart mainDocumentPart = wordDocument.MainDocumentPart; + + // Get a list of drawings (images) + IEnumerable drawings = mainDocumentPart.Document.Descendants().ToList(); + + /* + * FIXME: Look on how we can improve this loop operation. + */ + foreach (Drawing drawing in drawings) + { + DocProperties docProperty = drawing.Descendants().FirstOrDefault(); + + // If drawing / image name is present in imagePlaceholders dictionary, then replace image + if (docProperty != null && imagePlaceholders.ContainsKey(docProperty.Name)) + { + List drawingBlips = drawing.Descendants().ToList(); + + foreach (Blip blip in drawingBlips) + { + OpenXmlPart imagePart = wordDocument.MainDocumentPart.GetPartById(blip.Embed); + + using (BinaryWriter writer = new BinaryWriter(imagePart.GetStream())) + { + string imagePath = imagePlaceholders[docProperty.Name]; + + /* + * WebClient has been deprecated and we need to use HTTPClient. + * This involves the methods to be asynchronous. + */ + using (WebClient webClient = new WebClient()) + { + writer.Write(webClient.DownloadData(imagePath)); + } + } + } + } + } + } + + // Overwrite the output file + FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); + memoryStream.WriteTo(fileStream); + fileStream.Close(); + memoryStream.Close(); + } } \ No newline at end of file From 71073b1ece7335e1253baf5c14bd91f14ded481b Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 15 Jun 2025 14:52:09 +0530 Subject: [PATCH 02/25] chore: apply dotnet format changes --- .../Controllers/PdfController.cs | 414 +++++++++--------- .../Controllers/WordController.cs | 312 ++++++------- DocumentService.API/DotEnv.cs | 70 +-- .../Helpers/AuthenticationHelper.cs | 56 +-- .../Helpers/AutoMappingProfile.cs | 26 +- .../Helpers/Base64StringHelper.cs | 62 +-- .../Helpers/CommonMethodsHelper.cs | 52 +-- DocumentService.API/Models/BaseResponse.cs | 66 +-- .../Models/PdfGenerationRequestDTO.cs | 26 +- .../Models/WordGenerationRequestDTO.cs | 48 +- DocumentService.API/Program.cs | 274 ++++++------ 11 files changed, 703 insertions(+), 703 deletions(-) diff --git a/DocumentService.API/Controllers/PdfController.cs b/DocumentService.API/Controllers/PdfController.cs index 0413daa..1436eff 100644 --- a/DocumentService.API/Controllers/PdfController.cs +++ b/DocumentService.API/Controllers/PdfController.cs @@ -1,207 +1,207 @@ -using DocumentService.Pdf; -using DocumentService.API.Helpers; -using DocumentService.API.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Authorization; - -namespace DocumentService.API.Controllers; - -[Route("api")] -[ApiController] -public class PdfController : ControllerBase -{ - private readonly IConfiguration _configuration; - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly ILogger _logger; - - public PdfController(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, ILogger logger) - { - this._configuration = configuration; - this._hostingEnvironment = hostingEnvironment; - this._logger = logger; - } - - [HttpPost] - [Authorize] - [Route("pdf/GeneratePdfUsingHtml")] - public async Task> GeneratePdf(PdfGenerationRequestDTO request) - { - BaseResponse response = new BaseResponse(ResponseStatus.Fail); - - try - { - // Generate filepath to save base64 html template - string htmlTemplateFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:HTML").Value, - CommonMethodsHelper.GenerateRandomFileName("html") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(htmlTemplateFilePath); - - // Save base64 html template to inputs directory - await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, htmlTemplateFilePath, this._configuration); - - // Initialize tools and output filepaths - string htmlToPDfToolsFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value - ); - - string outputFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:PDF").Value, - CommonMethodsHelper.GenerateRandomFileName("pdf") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); - - // Generate and save pdf in output directory - PdfDocumentGenerator.GeneratePdf( - htmlToPDfToolsFilePath, - htmlTemplateFilePath, - request.DocumentData.Placeholders, - outputFilePath, - isEjsTemplate: false, - serializedEjsDataJson: null - ); - - // Convert pdf file in output directory to base64 string - string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); - - // Return response - response.Status = ResponseStatus.Success; - response.Base64 = outputBase64String; - response.Message = "PDF generated successfully"; - return this.Ok(response); - } - catch (BadHttpRequestException ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FormatException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Error converting base64 string to file"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FileNotFoundException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Unable to load file saved from base64 string"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - catch (Exception ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - } - - [HttpPost] - [Authorize] - [Route("pdf/GeneratePdfUsingEjs")] - public async Task> GeneratePdfUsingEjs(PdfGenerationRequestDTO request) - { - BaseResponse response = new BaseResponse(ResponseStatus.Fail); - - try - { - // Generate filepath to save base64 html template - string ejsTemplateFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:EJS").Value, - CommonMethodsHelper.GenerateRandomFileName("ejs") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(ejsTemplateFilePath); - - // Save base64 html template to inputs directory - await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, ejsTemplateFilePath, this._configuration); - - // Initialize tools and output filepaths - string ejsToPDfToolsFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value - ); - - string outputFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:PDF").Value, - CommonMethodsHelper.GenerateRandomFileName("pdf") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); - - // Generate and save pdf in output directory - PdfDocumentGenerator.GeneratePdf( - ejsToPDfToolsFilePath, - ejsTemplateFilePath, - request.DocumentData?.Placeholders, - outputFilePath, - isEjsTemplate: true, - serializedEjsDataJson: request.SerializedEjsDataJson - ); - - // Convert pdf file in output directory to base64 string - string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); - - // Return response - response.Status = ResponseStatus.Success; - response.Base64 = outputBase64String; - response.Message = "PDF generated successfully"; - return this.Ok(response); - } - catch (BadHttpRequestException ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FormatException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Error converting base64 string to file"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FileNotFoundException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Unable to load file saved from base64 string"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - catch (Exception ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - } -} +using DocumentService.Pdf; +using DocumentService.API.Helpers; +using DocumentService.API.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + +namespace DocumentService.API.Controllers; + +[Route("api")] +[ApiController] +public class PdfController : ControllerBase +{ + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ILogger _logger; + + public PdfController(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, ILogger logger) + { + this._configuration = configuration; + this._hostingEnvironment = hostingEnvironment; + this._logger = logger; + } + + [HttpPost] + [Authorize] + [Route("pdf/GeneratePdfUsingHtml")] + public async Task> GeneratePdf(PdfGenerationRequestDTO request) + { + BaseResponse response = new BaseResponse(ResponseStatus.Fail); + + try + { + // Generate filepath to save base64 html template + string htmlTemplateFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:HTML").Value, + CommonMethodsHelper.GenerateRandomFileName("html") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(htmlTemplateFilePath); + + // Save base64 html template to inputs directory + await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, htmlTemplateFilePath, this._configuration); + + // Initialize tools and output filepaths + string htmlToPDfToolsFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value + ); + + string outputFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:PDF").Value, + CommonMethodsHelper.GenerateRandomFileName("pdf") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); + + // Generate and save pdf in output directory + PdfDocumentGenerator.GeneratePdf( + htmlToPDfToolsFilePath, + htmlTemplateFilePath, + request.DocumentData.Placeholders, + outputFilePath, + isEjsTemplate: false, + serializedEjsDataJson: null + ); + + // Convert pdf file in output directory to base64 string + string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); + + // Return response + response.Status = ResponseStatus.Success; + response.Base64 = outputBase64String; + response.Message = "PDF generated successfully"; + return this.Ok(response); + } + catch (BadHttpRequestException ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FormatException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Error converting base64 string to file"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FileNotFoundException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Unable to load file saved from base64 string"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + catch (Exception ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + } + + [HttpPost] + [Authorize] + [Route("pdf/GeneratePdfUsingEjs")] + public async Task> GeneratePdfUsingEjs(PdfGenerationRequestDTO request) + { + BaseResponse response = new BaseResponse(ResponseStatus.Fail); + + try + { + // Generate filepath to save base64 html template + string ejsTemplateFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:EJS").Value, + CommonMethodsHelper.GenerateRandomFileName("ejs") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(ejsTemplateFilePath); + + // Save base64 html template to inputs directory + await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, ejsTemplateFilePath, this._configuration); + + // Initialize tools and output filepaths + string ejsToPDfToolsFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value + ); + + string outputFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:PDF").Value, + CommonMethodsHelper.GenerateRandomFileName("pdf") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); + + // Generate and save pdf in output directory + PdfDocumentGenerator.GeneratePdf( + ejsToPDfToolsFilePath, + ejsTemplateFilePath, + request.DocumentData?.Placeholders, + outputFilePath, + isEjsTemplate: true, + serializedEjsDataJson: request.SerializedEjsDataJson + ); + + // Convert pdf file in output directory to base64 string + string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); + + // Return response + response.Status = ResponseStatus.Success; + response.Base64 = outputBase64String; + response.Message = "PDF generated successfully"; + return this.Ok(response); + } + catch (BadHttpRequestException ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FormatException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Error converting base64 string to file"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FileNotFoundException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Unable to load file saved from base64 string"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + catch (Exception ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + } +} diff --git a/DocumentService.API/Controllers/WordController.cs b/DocumentService.API/Controllers/WordController.cs index 33601a8..c888b91 100644 --- a/DocumentService.API/Controllers/WordController.cs +++ b/DocumentService.API/Controllers/WordController.cs @@ -1,156 +1,156 @@ -using AutoMapper; -using DocumentService.Word; -using DocumentService.Word.Models; -using DocumentService.API.Helpers; -using DocumentService.API.Models; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Authorization; - -namespace DocumentService.API.Controllers; - -[Route("api")] -[ApiController] -public class WordController : ControllerBase -{ - private readonly IConfiguration _configuration; - private readonly IWebHostEnvironment _hostingEnvironment; - private readonly ILogger _logger; - private readonly IMapper _mapper; - - public WordController(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, ILogger logger, IMapper mapper) - { - this._configuration = configuration; - this._hostingEnvironment = hostingEnvironment; - this._logger = logger; - this._mapper = mapper; - } - - [HttpPost] - [Authorize] - [Route("word/GenerateWordDocument")] - public async Task> GenerateWord(WordGenerationRequestDTO request) - { - BaseResponse response = new BaseResponse(ResponseStatus.Fail); - - try - { - // Generate filepath to save base64 docx template - string docxTemplateFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, - CommonMethodsHelper.GenerateRandomFileName("docx") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(docxTemplateFilePath); - - // Save docx template to inputs directory - await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, docxTemplateFilePath, this._configuration); - - // Initialize output filepath - string outputFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, - CommonMethodsHelper.GenerateRandomFileName("docx") - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); - - // Handle image placeholder data in request - foreach (WordContentDataRequestDTO placeholder in request.DocumentData.Placeholders) - { - if (placeholder.ContentType == ContentType.Image) - { - if (string.IsNullOrWhiteSpace(placeholder.ImageExtension)) - { - throw new BadHttpRequestException("Image extension is required for image content data"); - } - - if (string.IsNullOrWhiteSpace(placeholder.Content)) - { - throw new BadHttpRequestException("Image content data is required"); - } - - // Remove '.' from image extension if present - placeholder.ImageExtension = placeholder.ImageExtension.Replace(".", string.Empty); - - // Generate a random image file name and its path - string imageFilePath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, - this._configuration.GetSection("TEMPORARY_FILE_PATHS:IMAGES").Value, - CommonMethodsHelper.GenerateRandomFileName(placeholder.ImageExtension) - ); - - CommonMethodsHelper.CreateDirectoryIfNotExists(imageFilePath); - - // Save image content base64 string to inputs directory - await Base64StringHelper.SaveBase64StringToFilePath(placeholder.Content, imageFilePath, this._configuration); - - // Replace placeholder content with image file path - placeholder.Content = imageFilePath; - } - } - - // Map document data in request to word library model class - DocumentData documentData = new DocumentData - { - Placeholders = this._mapper.Map>(request.DocumentData.Placeholders), - TablesData = request.DocumentData.TablesData - }; - - // Generate and save output docx in output directory - WordDocumentGenerator.GenerateDocumentByTemplate( - docxTemplateFilePath, - documentData, - outputFilePath - ); - - // Convert docx file in output directory to base64 string - string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); - - // Return response - response.Status = ResponseStatus.Success; - response.Base64 = outputBase64String; - response.Message = "Word document generated successfully"; - return this.Ok(response); - } - catch (BadHttpRequestException ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FormatException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Error converting base64 string to file"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.BadRequest(response); - } - catch (FileNotFoundException ex) - { - response.Status = ResponseStatus.Error; - response.Message = "Unable to load file saved from base64 string"; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - catch (Exception ex) - { - response.Status = ResponseStatus.Error; - response.Message = ex.Message; - this._logger.LogError(ex.Message); - this._logger.LogError(ex.StackTrace); - return this.StatusCode(StatusCodes.Status500InternalServerError, response); - } - } -} +using AutoMapper; +using DocumentService.Word; +using DocumentService.Word.Models; +using DocumentService.API.Helpers; +using DocumentService.API.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; + +namespace DocumentService.API.Controllers; + +[Route("api")] +[ApiController] +public class WordController : ControllerBase +{ + private readonly IConfiguration _configuration; + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly ILogger _logger; + private readonly IMapper _mapper; + + public WordController(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, ILogger logger, IMapper mapper) + { + this._configuration = configuration; + this._hostingEnvironment = hostingEnvironment; + this._logger = logger; + this._mapper = mapper; + } + + [HttpPost] + [Authorize] + [Route("word/GenerateWordDocument")] + public async Task> GenerateWord(WordGenerationRequestDTO request) + { + BaseResponse response = new BaseResponse(ResponseStatus.Fail); + + try + { + // Generate filepath to save base64 docx template + string docxTemplateFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, + CommonMethodsHelper.GenerateRandomFileName("docx") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(docxTemplateFilePath); + + // Save docx template to inputs directory + await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, docxTemplateFilePath, this._configuration); + + // Initialize output filepath + string outputFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:OUTPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, + CommonMethodsHelper.GenerateRandomFileName("docx") + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(outputFilePath); + + // Handle image placeholder data in request + foreach (WordContentDataRequestDTO placeholder in request.DocumentData.Placeholders) + { + if (placeholder.ContentType == ContentType.Image) + { + if (string.IsNullOrWhiteSpace(placeholder.ImageExtension)) + { + throw new BadHttpRequestException("Image extension is required for image content data"); + } + + if (string.IsNullOrWhiteSpace(placeholder.Content)) + { + throw new BadHttpRequestException("Image content data is required"); + } + + // Remove '.' from image extension if present + placeholder.ImageExtension = placeholder.ImageExtension.Replace(".", string.Empty); + + // Generate a random image file name and its path + string imageFilePath = Path.Combine( + this._hostingEnvironment.WebRootPath, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:INPUT").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:WORD").Value, + this._configuration.GetSection("TEMPORARY_FILE_PATHS:IMAGES").Value, + CommonMethodsHelper.GenerateRandomFileName(placeholder.ImageExtension) + ); + + CommonMethodsHelper.CreateDirectoryIfNotExists(imageFilePath); + + // Save image content base64 string to inputs directory + await Base64StringHelper.SaveBase64StringToFilePath(placeholder.Content, imageFilePath, this._configuration); + + // Replace placeholder content with image file path + placeholder.Content = imageFilePath; + } + } + + // Map document data in request to word library model class + DocumentData documentData = new DocumentData + { + Placeholders = this._mapper.Map>(request.DocumentData.Placeholders), + TablesData = request.DocumentData.TablesData + }; + + // Generate and save output docx in output directory + WordDocumentGenerator.GenerateDocumentByTemplate( + docxTemplateFilePath, + documentData, + outputFilePath + ); + + // Convert docx file in output directory to base64 string + string outputBase64String = await Base64StringHelper.ConvertFileToBase64String(outputFilePath); + + // Return response + response.Status = ResponseStatus.Success; + response.Base64 = outputBase64String; + response.Message = "Word document generated successfully"; + return this.Ok(response); + } + catch (BadHttpRequestException ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FormatException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Error converting base64 string to file"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.BadRequest(response); + } + catch (FileNotFoundException ex) + { + response.Status = ResponseStatus.Error; + response.Message = "Unable to load file saved from base64 string"; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + catch (Exception ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + } +} diff --git a/DocumentService.API/DotEnv.cs b/DocumentService.API/DotEnv.cs index 2b5b4ce..45e2a4a 100644 --- a/DocumentService.API/DotEnv.cs +++ b/DocumentService.API/DotEnv.cs @@ -1,35 +1,35 @@ -namespace DocumentService.API; - -public static class DotEnv -{ - public static void Load(string filePath) - { - if (!File.Exists(filePath)) - { - return; - } - - foreach (string line in File.ReadAllLines(filePath)) - { - // Check if the line contains '=' - int equalsIndex = line.IndexOf('='); - if (equalsIndex == -1) - { - continue; // Skip lines without '=' - } - - string key = line.Substring(0, equalsIndex).Trim(); - string value = line.Substring(equalsIndex + 1).Trim(); - - // Check if the value starts and ends with double quotation marks - if (value.StartsWith("\"") && value.EndsWith("\"")) - { - // Remove the double quotation marks - value = value[1..^1]; - } - - Environment.SetEnvironmentVariable(key, value); - } - } - -} +namespace DocumentService.API; + +public static class DotEnv +{ + public static void Load(string filePath) + { + if (!File.Exists(filePath)) + { + return; + } + + foreach (string line in File.ReadAllLines(filePath)) + { + // Check if the line contains '=' + int equalsIndex = line.IndexOf('='); + if (equalsIndex == -1) + { + continue; // Skip lines without '=' + } + + string key = line.Substring(0, equalsIndex).Trim(); + string value = line.Substring(equalsIndex + 1).Trim(); + + // Check if the value starts and ends with double quotation marks + if (value.StartsWith("\"") && value.EndsWith("\"")) + { + // Remove the double quotation marks + value = value[1..^1]; + } + + Environment.SetEnvironmentVariable(key, value); + } + } + +} diff --git a/DocumentService.API/Helpers/AuthenticationHelper.cs b/DocumentService.API/Helpers/AuthenticationHelper.cs index ed0dee9..5104e65 100644 --- a/DocumentService.API/Helpers/AuthenticationHelper.cs +++ b/DocumentService.API/Helpers/AuthenticationHelper.cs @@ -1,28 +1,28 @@ -using Microsoft.IdentityModel.Tokens; -using System.IdentityModel.Tokens.Jwt; -using System.Text; -using System.Security.Claims; - -namespace DocumentService.API.Helpers; - -public class AuthenticationHelper -{ - // Function to generate non expiry jwt token based on email - public static string JwtTokenGenerator(string LoginEmail) - { - SymmetricSecurityKey secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( - Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new InvalidOperationException("No JWT key specified") - )); - SigningCredentials signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); - - JwtSecurityToken tokenOptions = new JwtSecurityToken( - claims: new List() - { - new(ClaimTypes.Email, LoginEmail), - }, - signingCredentials: signinCredentials - ); - - return new JwtSecurityTokenHandler().WriteToken(tokenOptions); - } -} +using Microsoft.IdentityModel.Tokens; +using System.IdentityModel.Tokens.Jwt; +using System.Text; +using System.Security.Claims; + +namespace DocumentService.API.Helpers; + +public class AuthenticationHelper +{ + // Function to generate non expiry jwt token based on email + public static string JwtTokenGenerator(string LoginEmail) + { + SymmetricSecurityKey secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes( + Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new InvalidOperationException("No JWT key specified") + )); + SigningCredentials signinCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); + + JwtSecurityToken tokenOptions = new JwtSecurityToken( + claims: new List() + { + new(ClaimTypes.Email, LoginEmail), + }, + signingCredentials: signinCredentials + ); + + return new JwtSecurityTokenHandler().WriteToken(tokenOptions); + } +} diff --git a/DocumentService.API/Helpers/AutoMappingProfile.cs b/DocumentService.API/Helpers/AutoMappingProfile.cs index a9cef5c..d6ddaae 100644 --- a/DocumentService.API/Helpers/AutoMappingProfile.cs +++ b/DocumentService.API/Helpers/AutoMappingProfile.cs @@ -1,13 +1,13 @@ -using AutoMapper; -using DocumentService.Word.Models; -using DocumentService.API.Models; - -namespace DocumentService.API.Helpers; - -public class AutoMappingProfile : Profile -{ - public AutoMappingProfile() - { - this.CreateMap(); - } -} +using AutoMapper; +using DocumentService.Word.Models; +using DocumentService.API.Models; + +namespace DocumentService.API.Helpers; + +public class AutoMappingProfile : Profile +{ + public AutoMappingProfile() + { + this.CreateMap(); + } +} diff --git a/DocumentService.API/Helpers/Base64StringHelper.cs b/DocumentService.API/Helpers/Base64StringHelper.cs index 9984908..9d0368d 100644 --- a/DocumentService.API/Helpers/Base64StringHelper.cs +++ b/DocumentService.API/Helpers/Base64StringHelper.cs @@ -1,31 +1,31 @@ -namespace DocumentService.API.Helpers; - -public static class Base64StringHelper -{ - public static async Task SaveBase64StringToFilePath(string base64String, string filePath, IConfiguration configuration) - { - byte[] data = Convert.FromBase64String(base64String); - - long uploadFileSizeLimitBytes = Convert.ToInt64(configuration.GetSection("CONFIG:UPLOAD_FILE_SIZE_LIMIT_BYTES").Value); - - if (data.LongLength > uploadFileSizeLimitBytes) - { - throw new BadHttpRequestException("Uploaded file is too large"); - } - - await File.WriteAllBytesAsync(filePath, data); - } - - public static async Task ConvertFileToBase64String(string filePath) - { - if (File.Exists(filePath)) - { - byte[] fileData = await File.ReadAllBytesAsync(filePath); - return Convert.ToBase64String(fileData); - } - else - { - throw new FileNotFoundException("The file does not exist: " + filePath); - } - } -} +namespace DocumentService.API.Helpers; + +public static class Base64StringHelper +{ + public static async Task SaveBase64StringToFilePath(string base64String, string filePath, IConfiguration configuration) + { + byte[] data = Convert.FromBase64String(base64String); + + long uploadFileSizeLimitBytes = Convert.ToInt64(configuration.GetSection("CONFIG:UPLOAD_FILE_SIZE_LIMIT_BYTES").Value); + + if (data.LongLength > uploadFileSizeLimitBytes) + { + throw new BadHttpRequestException("Uploaded file is too large"); + } + + await File.WriteAllBytesAsync(filePath, data); + } + + public static async Task ConvertFileToBase64String(string filePath) + { + if (File.Exists(filePath)) + { + byte[] fileData = await File.ReadAllBytesAsync(filePath); + return Convert.ToBase64String(fileData); + } + else + { + throw new FileNotFoundException("The file does not exist: " + filePath); + } + } +} diff --git a/DocumentService.API/Helpers/CommonMethodsHelper.cs b/DocumentService.API/Helpers/CommonMethodsHelper.cs index dc9006d..eaac7f5 100644 --- a/DocumentService.API/Helpers/CommonMethodsHelper.cs +++ b/DocumentService.API/Helpers/CommonMethodsHelper.cs @@ -1,26 +1,26 @@ -namespace DocumentService.API.Helpers; - -public static class CommonMethodsHelper -{ - public static void CreateDirectoryIfNotExists(string filePath) - { - // Get directory name of the file - // If path is a file name only, directory name will be an empty string - string directoryName = Path.GetDirectoryName(filePath); - - if (!string.IsNullOrWhiteSpace(directoryName)) - { - if (!Directory.Exists(directoryName)) - { - // Create all directories on the path that don't already exist - Directory.CreateDirectory(directoryName); - } - } - } - - public static string GenerateRandomFileName(string fileExtension) - { - string randomFileName = Path.GetRandomFileName().Replace(".", string.Empty); - return $"{randomFileName}-{Guid.NewGuid()}.{fileExtension}"; - } -} +namespace DocumentService.API.Helpers; + +public static class CommonMethodsHelper +{ + public static void CreateDirectoryIfNotExists(string filePath) + { + // Get directory name of the file + // If path is a file name only, directory name will be an empty string + string directoryName = Path.GetDirectoryName(filePath); + + if (!string.IsNullOrWhiteSpace(directoryName)) + { + if (!Directory.Exists(directoryName)) + { + // Create all directories on the path that don't already exist + Directory.CreateDirectory(directoryName); + } + } + } + + public static string GenerateRandomFileName(string fileExtension) + { + string randomFileName = Path.GetRandomFileName().Replace(".", string.Empty); + return $"{randomFileName}-{Guid.NewGuid()}.{fileExtension}"; + } +} diff --git a/DocumentService.API/Models/BaseResponse.cs b/DocumentService.API/Models/BaseResponse.cs index 0422bbe..e6d5954 100644 --- a/DocumentService.API/Models/BaseResponse.cs +++ b/DocumentService.API/Models/BaseResponse.cs @@ -1,33 +1,33 @@ -using Microsoft.AspNetCore.Mvc; - -namespace DocumentService.API.Models; - -public enum ResponseStatus -{ - Success, - Fail, - Error -} - -public class BaseResponse -{ - public BaseResponse(ResponseStatus status) => this.Status = status; - public ResponseStatus? Status { get; set; } - public string? Base64 { get; set; } - public string? AuthToken { get; set; } - public string? Message { get; set; } - public string? StackTrace { get; set; } -} - -public class ModelValidationBadRequest -{ - public static BadRequestObjectResult ModelValidationErrorResponse(ActionContext actionContext) - { - return new BadRequestObjectResult(actionContext.ModelState - .Where(modelError => modelError.Value.Errors.Any()) - .Select(modelError => new BaseResponse(ResponseStatus.Error) - { - Message = modelError.Value.Errors.FirstOrDefault().ErrorMessage - }).FirstOrDefault()); - } -} +using Microsoft.AspNetCore.Mvc; + +namespace DocumentService.API.Models; + +public enum ResponseStatus +{ + Success, + Fail, + Error +} + +public class BaseResponse +{ + public BaseResponse(ResponseStatus status) => this.Status = status; + public ResponseStatus? Status { get; set; } + public string? Base64 { get; set; } + public string? AuthToken { get; set; } + public string? Message { get; set; } + public string? StackTrace { get; set; } +} + +public class ModelValidationBadRequest +{ + public static BadRequestObjectResult ModelValidationErrorResponse(ActionContext actionContext) + { + return new BadRequestObjectResult(actionContext.ModelState + .Where(modelError => modelError.Value.Errors.Any()) + .Select(modelError => new BaseResponse(ResponseStatus.Error) + { + Message = modelError.Value.Errors.FirstOrDefault().ErrorMessage + }).FirstOrDefault()); + } +} diff --git a/DocumentService.API/Models/PdfGenerationRequestDTO.cs b/DocumentService.API/Models/PdfGenerationRequestDTO.cs index 8408128..7b0b602 100644 --- a/DocumentService.API/Models/PdfGenerationRequestDTO.cs +++ b/DocumentService.API/Models/PdfGenerationRequestDTO.cs @@ -1,13 +1,13 @@ -using DocumentService.Pdf.Models; -using System.ComponentModel.DataAnnotations; - -namespace DocumentService.API.Models; - -public class PdfGenerationRequestDTO -{ - [Required(ErrorMessage = "Base64 string for PDF template is required")] - public string? Base64 { get; set; } - [Required(ErrorMessage = "Data to be modified in PDF is required")] - public DocumentData? DocumentData { get; set; } - public string? SerializedEjsDataJson { get; set; } -} +using DocumentService.Pdf.Models; +using System.ComponentModel.DataAnnotations; + +namespace DocumentService.API.Models; + +public class PdfGenerationRequestDTO +{ + [Required(ErrorMessage = "Base64 string for PDF template is required")] + public string? Base64 { get; set; } + [Required(ErrorMessage = "Data to be modified in PDF is required")] + public DocumentData? DocumentData { get; set; } + public string? SerializedEjsDataJson { get; set; } +} diff --git a/DocumentService.API/Models/WordGenerationRequestDTO.cs b/DocumentService.API/Models/WordGenerationRequestDTO.cs index c2462a1..1fd1bca 100644 --- a/DocumentService.API/Models/WordGenerationRequestDTO.cs +++ b/DocumentService.API/Models/WordGenerationRequestDTO.cs @@ -1,24 +1,24 @@ -using DocumentService.Word.Models; -using System.ComponentModel.DataAnnotations; - -namespace DocumentService.API.Models; - - -public class WordGenerationRequestDTO -{ - [Required(ErrorMessage = "Base64 string for Word template is required")] - public string? Base64 { get; set; } - [Required(ErrorMessage = "Data to be modified in Word file is required")] - public WordDocumentDataRequestDTO? DocumentData { get; set; } -} - -public class WordContentDataRequestDTO : ContentData -{ - public string? ImageExtension { get; set; } -} - -public class WordDocumentDataRequestDTO -{ - public List Placeholders { get; set; } - public List TablesData { get; set; } -} +using DocumentService.Word.Models; +using System.ComponentModel.DataAnnotations; + +namespace DocumentService.API.Models; + + +public class WordGenerationRequestDTO +{ + [Required(ErrorMessage = "Base64 string for Word template is required")] + public string? Base64 { get; set; } + [Required(ErrorMessage = "Data to be modified in Word file is required")] + public WordDocumentDataRequestDTO? DocumentData { get; set; } +} + +public class WordContentDataRequestDTO : ContentData +{ + public string? ImageExtension { get; set; } +} + +public class WordDocumentDataRequestDTO +{ + public List Placeholders { get; set; } + public List TablesData { get; set; } +} diff --git a/DocumentService.API/Program.cs b/DocumentService.API/Program.cs index 728b1ac..6c4ec49 100644 --- a/DocumentService.API/Program.cs +++ b/DocumentService.API/Program.cs @@ -1,137 +1,137 @@ -using DocumentService.API.Models; -using Microsoft.AspNetCore.Mvc; -using Serilog.Events; -using Serilog; -using System.Text.Json.Serialization; -using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.OpenApi.Models; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.IdentityModel.Tokens; -using System.Text; -using Swashbuckle.AspNetCore.Filters; - -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); - -// Controller Services -builder.Services.AddControllers(options => options.Filters.Add(new ProducesAttribute("application/json"))) - .AddJsonOptions(options => - { - options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); - }); - -// Load .env file -string root = Directory.GetCurrentDirectory(); -string dotenv = Path.GetFullPath(Path.Combine(root, "..", ".env")); -DocumentService.API.DotEnv.Load(dotenv); - -// Configure request size limit -long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value); - -// Configure request size for Kestrel server - ASP.NET Core project templates use Kestrel by default when not hosted with IIS -builder.Services.Configure(options => -{ - options.Limits.MaxRequestBodySize = requestBodySizeLimitBytes; -}); - -// Configure request size for IIS server -builder.Services.Configure(options => -{ - options.MaxRequestBodySize = requestBodySizeLimitBytes; -}); - -// AutoMapper Services -builder.Services.AddAutoMapper(typeof(Program)); - -// Swagger UI Services -builder.Services.AddSwaggerGen(options => -{ - options.SwaggerDoc("v1", new OpenApiInfo { Title = "DocumentService API", Version = "v1" }); - - options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme - { - Name = "Authorization", - Description = "Standard Authorization header using the Bearer Scheme (\"bearer {token}\")", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey - }); - - options.OperationFilter(); -}); - -// Authentication -builder.Services.AddAuthentication(options => -{ - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; -}) -.AddJwtBearer(options => -{ - string JWT_KEY = Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new InvalidOperationException("No JWT key specified"); - - options.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY)), - ValidateIssuer = false, - ValidateAudience = false, - // Following code is to allow us to custom handle expiry - // Here check expiry as nullable - ClockSkew = TimeSpan.Zero, - ValidateLifetime = true, - LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) => - { - // Clone the validation parameters, and remove the defult lifetime validator - TokenValidationParameters clonedParameters = validationParameters.Clone(); - clonedParameters.LifetimeValidator = null; - - // If token expiry time is not null, then validate lifetime with skewed clock - if (expires != null) - { - Validators.ValidateLifetime(notBefore, expires, securityToken, clonedParameters); - } - - return true; - } - }; -}); - -// Configure Error Response from Model Validations -builder.Services.AddMvc().ConfigureApiBehaviorOptions(options => -{ - options.InvalidModelStateResponseFactory = actionContext => - { - return ModelValidationBadRequest.ModelValidationErrorResponse(actionContext); - }; -}); - -// Logging service Serilogs -builder.Logging.AddSerilog(); -Log.Logger = new LoggerConfiguration() - .WriteTo.File( - path: "wwwroot/logs/log-.txt", - outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}{NewLine}{NewLine}", - rollingInterval: RollingInterval.Day, - restrictedToMinimumLevel: LogEventLevel.Information - ).CreateLogger(); - -WebApplication app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} - -app.UseStaticFiles(); - -app.UseHttpsRedirection(); - -app.UseAuthentication(); - -app.UseAuthorization(); - -app.MapControllers(); - -app.Run(); +using DocumentService.API.Models; +using Microsoft.AspNetCore.Mvc; +using Serilog.Events; +using Serilog; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.OpenApi.Models; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using System.Text; +using Swashbuckle.AspNetCore.Filters; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Controller Services +builder.Services.AddControllers(options => options.Filters.Add(new ProducesAttribute("application/json"))) + .AddJsonOptions(options => + { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); + +// Load .env file +string root = Directory.GetCurrentDirectory(); +string dotenv = Path.GetFullPath(Path.Combine(root, "..", ".env")); +DocumentService.API.DotEnv.Load(dotenv); + +// Configure request size limit +long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value); + +// Configure request size for Kestrel server - ASP.NET Core project templates use Kestrel by default when not hosted with IIS +builder.Services.Configure(options => +{ + options.Limits.MaxRequestBodySize = requestBodySizeLimitBytes; +}); + +// Configure request size for IIS server +builder.Services.Configure(options => +{ + options.MaxRequestBodySize = requestBodySizeLimitBytes; +}); + +// AutoMapper Services +builder.Services.AddAutoMapper(typeof(Program)); + +// Swagger UI Services +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc("v1", new OpenApiInfo { Title = "DocumentService API", Version = "v1" }); + + options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme + { + Name = "Authorization", + Description = "Standard Authorization header using the Bearer Scheme (\"bearer {token}\")", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey + }); + + options.OperationFilter(); +}); + +// Authentication +builder.Services.AddAuthentication(options => +{ + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(options => +{ + string JWT_KEY = Environment.GetEnvironmentVariable("JWT_KEY") ?? throw new InvalidOperationException("No JWT key specified"); + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(JWT_KEY)), + ValidateIssuer = false, + ValidateAudience = false, + // Following code is to allow us to custom handle expiry + // Here check expiry as nullable + ClockSkew = TimeSpan.Zero, + ValidateLifetime = true, + LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters) => + { + // Clone the validation parameters, and remove the defult lifetime validator + TokenValidationParameters clonedParameters = validationParameters.Clone(); + clonedParameters.LifetimeValidator = null; + + // If token expiry time is not null, then validate lifetime with skewed clock + if (expires != null) + { + Validators.ValidateLifetime(notBefore, expires, securityToken, clonedParameters); + } + + return true; + } + }; +}); + +// Configure Error Response from Model Validations +builder.Services.AddMvc().ConfigureApiBehaviorOptions(options => +{ + options.InvalidModelStateResponseFactory = actionContext => + { + return ModelValidationBadRequest.ModelValidationErrorResponse(actionContext); + }; +}); + +// Logging service Serilogs +builder.Logging.AddSerilog(); +Log.Logger = new LoggerConfiguration() + .WriteTo.File( + path: "wwwroot/logs/log-.txt", + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}{NewLine}{NewLine}", + rollingInterval: RollingInterval.Day, + restrictedToMinimumLevel: LogEventLevel.Information + ).CreateLogger(); + +WebApplication app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseStaticFiles(); + +app.UseHttpsRedirection(); + +app.UseAuthentication(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); From 9d714d615a16d0c865a7441c3a0e4538aecc4431 Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 15 Jun 2025 15:21:17 +0530 Subject: [PATCH 03/25] refactor: renamed project from DocumentService to OsmoDoc and updated all related namespaces --- .../Controllers/PdfController.cs | 8 ++++---- .../Controllers/WordController.cs | 10 +++++----- {DocumentService.API => OsmoDoc.API}/DotEnv.cs | 2 +- .../Helpers/AuthenticationHelper.cs | 2 +- .../Helpers/AutoMappingProfile.cs | 6 +++--- .../Helpers/Base64StringHelper.cs | 2 +- .../Helpers/CommonMethodsHelper.cs | 2 +- .../Models/BaseResponse.cs | 2 +- .../Models/PdfGenerationRequestDTO.cs | 4 ++-- .../Models/WordGenerationRequestDTO.cs | 4 ++-- .../OsmoDoc.API.csproj | 2 +- .../OsmoDoc.API.sln | 2 +- {DocumentService.API => OsmoDoc.API}/Program.cs | 6 +++--- .../Properties/launchSettings.json | 0 .../appsettings.Development.json | 0 .../appsettings.json | 0 .../wwwroot/Tools/wkhtmltopdf.exe | Bin DocumentService.sln => OsmoDoc.sln | 4 ++-- .../OsmoDoc.csproj | 0 .../Pdf/Models/ContentMetaData.cs | 2 +- .../Pdf/Models/DocumentData.cs | 2 +- .../Pdf/PdfDocumentGenerator.cs | 4 ++-- .../Word/Models/ContentData.cs | 2 +- .../Word/Models/DocumentData.cs | 2 +- {DocumentService => OsmoDoc}/Word/Models/Enums.cs | 2 +- .../Word/Models/TableData.cs | 2 +- .../Word/WordDocumentGenerator.cs | 4 ++-- 27 files changed, 38 insertions(+), 38 deletions(-) rename {DocumentService.API => OsmoDoc.API}/Controllers/PdfController.cs (96%) rename {DocumentService.API => OsmoDoc.API}/Controllers/WordController.cs (95%) rename {DocumentService.API => OsmoDoc.API}/DotEnv.cs (92%) rename {DocumentService.API => OsmoDoc.API}/Helpers/AuthenticationHelper.cs (93%) rename {DocumentService.API => OsmoDoc.API}/Helpers/AutoMappingProfile.cs (59%) rename {DocumentService.API => OsmoDoc.API}/Helpers/Base64StringHelper.cs (93%) rename {DocumentService.API => OsmoDoc.API}/Helpers/CommonMethodsHelper.cs (92%) rename {DocumentService.API => OsmoDoc.API}/Models/BaseResponse.cs (92%) rename {DocumentService.API => OsmoDoc.API}/Models/PdfGenerationRequestDTO.cs (81%) rename {DocumentService.API => OsmoDoc.API}/Models/WordGenerationRequestDTO.cs (86%) rename DocumentService.API/DocumentService.API.csproj => OsmoDoc.API/OsmoDoc.API.csproj (90%) rename DocumentService.API/DocumentService.API.sln => OsmoDoc.API/OsmoDoc.API.sln (86%) rename {DocumentService.API => OsmoDoc.API}/Program.cs (93%) rename {DocumentService.API => OsmoDoc.API}/Properties/launchSettings.json (100%) rename {DocumentService.API => OsmoDoc.API}/appsettings.Development.json (100%) rename {DocumentService.API => OsmoDoc.API}/appsettings.json (100%) rename {DocumentService.API => OsmoDoc.API}/wwwroot/Tools/wkhtmltopdf.exe (100%) rename DocumentService.sln => OsmoDoc.sln (80%) rename DocumentService/DocumentService.csproj => OsmoDoc/OsmoDoc.csproj (100%) rename {DocumentService => OsmoDoc}/Pdf/Models/ContentMetaData.cs (70%) rename {DocumentService => OsmoDoc}/Pdf/Models/DocumentData.cs (77%) rename {DocumentService => OsmoDoc}/Pdf/PdfDocumentGenerator.cs (96%) rename {DocumentService => OsmoDoc}/Word/Models/ContentData.cs (91%) rename {DocumentService => OsmoDoc}/Word/Models/DocumentData.cs (89%) rename {DocumentService => OsmoDoc}/Word/Models/Enums.cs (89%) rename {DocumentService => OsmoDoc}/Word/Models/TableData.cs (89%) rename {DocumentService => OsmoDoc}/Word/WordDocumentGenerator.cs (97%) diff --git a/DocumentService.API/Controllers/PdfController.cs b/OsmoDoc.API/Controllers/PdfController.cs similarity index 96% rename from DocumentService.API/Controllers/PdfController.cs rename to OsmoDoc.API/Controllers/PdfController.cs index 1436eff..de7f1e2 100644 --- a/DocumentService.API/Controllers/PdfController.cs +++ b/OsmoDoc.API/Controllers/PdfController.cs @@ -1,10 +1,10 @@ -using DocumentService.Pdf; -using DocumentService.API.Helpers; -using DocumentService.API.Models; +using OsmoDoc.Pdf; +using OsmoDoc.API.Helpers; +using OsmoDoc.API.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; -namespace DocumentService.API.Controllers; +namespace OsmoDoc.API.Controllers; [Route("api")] [ApiController] diff --git a/DocumentService.API/Controllers/WordController.cs b/OsmoDoc.API/Controllers/WordController.cs similarity index 95% rename from DocumentService.API/Controllers/WordController.cs rename to OsmoDoc.API/Controllers/WordController.cs index c888b91..e32095f 100644 --- a/DocumentService.API/Controllers/WordController.cs +++ b/OsmoDoc.API/Controllers/WordController.cs @@ -1,12 +1,12 @@ using AutoMapper; -using DocumentService.Word; -using DocumentService.Word.Models; -using DocumentService.API.Helpers; -using DocumentService.API.Models; +using OsmoDoc.Word; +using OsmoDoc.Word.Models; +using OsmoDoc.API.Helpers; +using OsmoDoc.API.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; -namespace DocumentService.API.Controllers; +namespace OsmoDoc.API.Controllers; [Route("api")] [ApiController] diff --git a/DocumentService.API/DotEnv.cs b/OsmoDoc.API/DotEnv.cs similarity index 92% rename from DocumentService.API/DotEnv.cs rename to OsmoDoc.API/DotEnv.cs index 45e2a4a..e0801f2 100644 --- a/DocumentService.API/DotEnv.cs +++ b/OsmoDoc.API/DotEnv.cs @@ -1,4 +1,4 @@ -namespace DocumentService.API; +namespace OsmoDoc.API; public static class DotEnv { diff --git a/DocumentService.API/Helpers/AuthenticationHelper.cs b/OsmoDoc.API/Helpers/AuthenticationHelper.cs similarity index 93% rename from DocumentService.API/Helpers/AuthenticationHelper.cs rename to OsmoDoc.API/Helpers/AuthenticationHelper.cs index 5104e65..04dc325 100644 --- a/DocumentService.API/Helpers/AuthenticationHelper.cs +++ b/OsmoDoc.API/Helpers/AuthenticationHelper.cs @@ -3,7 +3,7 @@ using System.Text; using System.Security.Claims; -namespace DocumentService.API.Helpers; +namespace OsmoDoc.API.Helpers; public class AuthenticationHelper { diff --git a/DocumentService.API/Helpers/AutoMappingProfile.cs b/OsmoDoc.API/Helpers/AutoMappingProfile.cs similarity index 59% rename from DocumentService.API/Helpers/AutoMappingProfile.cs rename to OsmoDoc.API/Helpers/AutoMappingProfile.cs index d6ddaae..269ba7b 100644 --- a/DocumentService.API/Helpers/AutoMappingProfile.cs +++ b/OsmoDoc.API/Helpers/AutoMappingProfile.cs @@ -1,8 +1,8 @@ using AutoMapper; -using DocumentService.Word.Models; -using DocumentService.API.Models; +using OsmoDoc.Word.Models; +using OsmoDoc.API.Models; -namespace DocumentService.API.Helpers; +namespace OsmoDoc.API.Helpers; public class AutoMappingProfile : Profile { diff --git a/DocumentService.API/Helpers/Base64StringHelper.cs b/OsmoDoc.API/Helpers/Base64StringHelper.cs similarity index 93% rename from DocumentService.API/Helpers/Base64StringHelper.cs rename to OsmoDoc.API/Helpers/Base64StringHelper.cs index 9d0368d..df48427 100644 --- a/DocumentService.API/Helpers/Base64StringHelper.cs +++ b/OsmoDoc.API/Helpers/Base64StringHelper.cs @@ -1,4 +1,4 @@ -namespace DocumentService.API.Helpers; +namespace OsmoDoc.API.Helpers; public static class Base64StringHelper { diff --git a/DocumentService.API/Helpers/CommonMethodsHelper.cs b/OsmoDoc.API/Helpers/CommonMethodsHelper.cs similarity index 92% rename from DocumentService.API/Helpers/CommonMethodsHelper.cs rename to OsmoDoc.API/Helpers/CommonMethodsHelper.cs index eaac7f5..fe3de2b 100644 --- a/DocumentService.API/Helpers/CommonMethodsHelper.cs +++ b/OsmoDoc.API/Helpers/CommonMethodsHelper.cs @@ -1,4 +1,4 @@ -namespace DocumentService.API.Helpers; +namespace OsmoDoc.API.Helpers; public static class CommonMethodsHelper { diff --git a/DocumentService.API/Models/BaseResponse.cs b/OsmoDoc.API/Models/BaseResponse.cs similarity index 92% rename from DocumentService.API/Models/BaseResponse.cs rename to OsmoDoc.API/Models/BaseResponse.cs index e6d5954..a2df3cc 100644 --- a/DocumentService.API/Models/BaseResponse.cs +++ b/OsmoDoc.API/Models/BaseResponse.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace DocumentService.API.Models; +namespace OsmoDoc.API.Models; public enum ResponseStatus { diff --git a/DocumentService.API/Models/PdfGenerationRequestDTO.cs b/OsmoDoc.API/Models/PdfGenerationRequestDTO.cs similarity index 81% rename from DocumentService.API/Models/PdfGenerationRequestDTO.cs rename to OsmoDoc.API/Models/PdfGenerationRequestDTO.cs index 7b0b602..f88a849 100644 --- a/DocumentService.API/Models/PdfGenerationRequestDTO.cs +++ b/OsmoDoc.API/Models/PdfGenerationRequestDTO.cs @@ -1,7 +1,7 @@ -using DocumentService.Pdf.Models; +using OsmoDoc.Pdf.Models; using System.ComponentModel.DataAnnotations; -namespace DocumentService.API.Models; +namespace OsmoDoc.API.Models; public class PdfGenerationRequestDTO { diff --git a/DocumentService.API/Models/WordGenerationRequestDTO.cs b/OsmoDoc.API/Models/WordGenerationRequestDTO.cs similarity index 86% rename from DocumentService.API/Models/WordGenerationRequestDTO.cs rename to OsmoDoc.API/Models/WordGenerationRequestDTO.cs index 1fd1bca..35acf13 100644 --- a/DocumentService.API/Models/WordGenerationRequestDTO.cs +++ b/OsmoDoc.API/Models/WordGenerationRequestDTO.cs @@ -1,7 +1,7 @@ -using DocumentService.Word.Models; +using OsmoDoc.Word.Models; using System.ComponentModel.DataAnnotations; -namespace DocumentService.API.Models; +namespace OsmoDoc.API.Models; public class WordGenerationRequestDTO diff --git a/DocumentService.API/DocumentService.API.csproj b/OsmoDoc.API/OsmoDoc.API.csproj similarity index 90% rename from DocumentService.API/DocumentService.API.csproj rename to OsmoDoc.API/OsmoDoc.API.csproj index bcd5563..b09bf6b 100644 --- a/DocumentService.API/DocumentService.API.csproj +++ b/OsmoDoc.API/OsmoDoc.API.csproj @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/DocumentService.API/DocumentService.API.sln b/OsmoDoc.API/OsmoDoc.API.sln similarity index 86% rename from DocumentService.API/DocumentService.API.sln rename to OsmoDoc.API/OsmoDoc.API.sln index ee22b37..94f94c8 100644 --- a/DocumentService.API/DocumentService.API.sln +++ b/OsmoDoc.API/OsmoDoc.API.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.7.34031.279 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentService.API", "DocumentService.API.csproj", "{A99B82C1-0758-4FDA-8D3B-5F11E99896F1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OsmoDoc.API", "OsmoDoc.API.csproj", "{A99B82C1-0758-4FDA-8D3B-5F11E99896F1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/DocumentService.API/Program.cs b/OsmoDoc.API/Program.cs similarity index 93% rename from DocumentService.API/Program.cs rename to OsmoDoc.API/Program.cs index 6c4ec49..bf4266b 100644 --- a/DocumentService.API/Program.cs +++ b/OsmoDoc.API/Program.cs @@ -1,4 +1,4 @@ -using DocumentService.API.Models; +using OsmoDoc.API.Models; using Microsoft.AspNetCore.Mvc; using Serilog.Events; using Serilog; @@ -23,7 +23,7 @@ // Load .env file string root = Directory.GetCurrentDirectory(); string dotenv = Path.GetFullPath(Path.Combine(root, "..", ".env")); -DocumentService.API.DotEnv.Load(dotenv); +OsmoDoc.API.DotEnv.Load(dotenv); // Configure request size limit long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value); @@ -46,7 +46,7 @@ // Swagger UI Services builder.Services.AddSwaggerGen(options => { - options.SwaggerDoc("v1", new OpenApiInfo { Title = "DocumentService API", Version = "v1" }); + options.SwaggerDoc("v1", new OpenApiInfo { Title = "OsmoDoc API", Version = "v1" }); options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme { diff --git a/DocumentService.API/Properties/launchSettings.json b/OsmoDoc.API/Properties/launchSettings.json similarity index 100% rename from DocumentService.API/Properties/launchSettings.json rename to OsmoDoc.API/Properties/launchSettings.json diff --git a/DocumentService.API/appsettings.Development.json b/OsmoDoc.API/appsettings.Development.json similarity index 100% rename from DocumentService.API/appsettings.Development.json rename to OsmoDoc.API/appsettings.Development.json diff --git a/DocumentService.API/appsettings.json b/OsmoDoc.API/appsettings.json similarity index 100% rename from DocumentService.API/appsettings.json rename to OsmoDoc.API/appsettings.json diff --git a/DocumentService.API/wwwroot/Tools/wkhtmltopdf.exe b/OsmoDoc.API/wwwroot/Tools/wkhtmltopdf.exe similarity index 100% rename from DocumentService.API/wwwroot/Tools/wkhtmltopdf.exe rename to OsmoDoc.API/wwwroot/Tools/wkhtmltopdf.exe diff --git a/DocumentService.sln b/OsmoDoc.sln similarity index 80% rename from DocumentService.sln rename to OsmoDoc.sln index 2a3de81..d4c665f 100644 --- a/DocumentService.sln +++ b/OsmoDoc.sln @@ -3,9 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33213.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentService", "DocumentService\DocumentService.csproj", "{AC14A26A-220C-487E-9D7B-BB0548D86318}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OsmoDoc", "OsmoDoc\OsmoDoc.csproj", "{AC14A26A-220C-487E-9D7B-BB0548D86318}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocumentService.API", "DocumentService.API\DocumentService.API.csproj", "{2D2738C1-032B-441A-9298-1722A4915BD4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OsmoDoc.API", "OsmoDoc.API\OsmoDoc.API.csproj", "{2D2738C1-032B-441A-9298-1722A4915BD4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/DocumentService/DocumentService.csproj b/OsmoDoc/OsmoDoc.csproj similarity index 100% rename from DocumentService/DocumentService.csproj rename to OsmoDoc/OsmoDoc.csproj diff --git a/DocumentService/Pdf/Models/ContentMetaData.cs b/OsmoDoc/Pdf/Models/ContentMetaData.cs similarity index 70% rename from DocumentService/Pdf/Models/ContentMetaData.cs rename to OsmoDoc/Pdf/Models/ContentMetaData.cs index fd8d22e..549f187 100644 --- a/DocumentService/Pdf/Models/ContentMetaData.cs +++ b/OsmoDoc/Pdf/Models/ContentMetaData.cs @@ -1,4 +1,4 @@ -namespace DocumentService.Pdf.Models; +namespace OsmoDoc.Pdf.Models; public class ContentMetaData { diff --git a/DocumentService/Pdf/Models/DocumentData.cs b/OsmoDoc/Pdf/Models/DocumentData.cs similarity index 77% rename from DocumentService/Pdf/Models/DocumentData.cs rename to OsmoDoc/Pdf/Models/DocumentData.cs index 3427bca..7aa919b 100644 --- a/DocumentService/Pdf/Models/DocumentData.cs +++ b/OsmoDoc/Pdf/Models/DocumentData.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace DocumentService.Pdf.Models; +namespace OsmoDoc.Pdf.Models; public class DocumentData { diff --git a/DocumentService/Pdf/PdfDocumentGenerator.cs b/OsmoDoc/Pdf/PdfDocumentGenerator.cs similarity index 96% rename from DocumentService/Pdf/PdfDocumentGenerator.cs rename to OsmoDoc/Pdf/PdfDocumentGenerator.cs index d293c4b..370b094 100644 --- a/DocumentService/Pdf/PdfDocumentGenerator.cs +++ b/OsmoDoc/Pdf/PdfDocumentGenerator.cs @@ -1,4 +1,4 @@ -using DocumentService.Pdf.Models; +using OsmoDoc.Pdf.Models; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.InteropServices; -namespace DocumentService.Pdf; +namespace OsmoDoc.Pdf; public class PdfDocumentGenerator { diff --git a/DocumentService/Word/Models/ContentData.cs b/OsmoDoc/Word/Models/ContentData.cs similarity index 91% rename from DocumentService/Word/Models/ContentData.cs rename to OsmoDoc/Word/Models/ContentData.cs index 38391ba..512eb79 100644 --- a/DocumentService/Word/Models/ContentData.cs +++ b/OsmoDoc/Word/Models/ContentData.cs @@ -1,4 +1,4 @@ -namespace DocumentService.Word.Models; +namespace OsmoDoc.Word.Models; /// diff --git a/DocumentService/Word/Models/DocumentData.cs b/OsmoDoc/Word/Models/DocumentData.cs similarity index 89% rename from DocumentService/Word/Models/DocumentData.cs rename to OsmoDoc/Word/Models/DocumentData.cs index 5b56a8e..ea0e978 100644 --- a/DocumentService/Word/Models/DocumentData.cs +++ b/OsmoDoc/Word/Models/DocumentData.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Text; -namespace DocumentService.Word.Models; +namespace OsmoDoc.Word.Models; /// diff --git a/DocumentService/Word/Models/Enums.cs b/OsmoDoc/Word/Models/Enums.cs similarity index 89% rename from DocumentService/Word/Models/Enums.cs rename to OsmoDoc/Word/Models/Enums.cs index 31fedc5..d957dad 100644 --- a/DocumentService/Word/Models/Enums.cs +++ b/OsmoDoc/Word/Models/Enums.cs @@ -1,4 +1,4 @@ -namespace DocumentService.Word.Models; +namespace OsmoDoc.Word.Models; /// diff --git a/DocumentService/Word/Models/TableData.cs b/OsmoDoc/Word/Models/TableData.cs similarity index 89% rename from DocumentService/Word/Models/TableData.cs rename to OsmoDoc/Word/Models/TableData.cs index dba7a72..6e47f98 100644 --- a/DocumentService/Word/Models/TableData.cs +++ b/OsmoDoc/Word/Models/TableData.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace DocumentService.Word.Models; +namespace OsmoDoc.Word.Models; /// diff --git a/DocumentService/Word/WordDocumentGenerator.cs b/OsmoDoc/Word/WordDocumentGenerator.cs similarity index 97% rename from DocumentService/Word/WordDocumentGenerator.cs rename to OsmoDoc/Word/WordDocumentGenerator.cs index 83198f5..ca7d2e4 100644 --- a/DocumentService/Word/WordDocumentGenerator.cs +++ b/OsmoDoc/Word/WordDocumentGenerator.cs @@ -2,7 +2,7 @@ using DocumentFormat.OpenXml.Drawing.Wordprocessing; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; -using DocumentService.Word.Models; +using OsmoDoc.Word.Models; using NPOI.XWPF.UserModel; using System; using System.Collections.Generic; @@ -11,7 +11,7 @@ using System.Net; using System.Text.RegularExpressions; -namespace DocumentService.Word; +namespace OsmoDoc.Word; /// /// Provides functionality to generate Word documents based on templates and data. From 513819c074e1fe01817af4449039441c74646045 Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 15 Jun 2025 15:23:49 +0530 Subject: [PATCH 04/25] refactor: update Dockerfile and docker-compose for OsmoDoc rename --- Dockerfile | 14 +++++++------- docker-compose.yaml | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8deee30..e4f3a8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,19 +10,19 @@ EXPOSE 5000 ENV BUILD_CONFIGURATION=Debug # Copy data -COPY ["DocumentService.API/DocumentService.API.csproj", "DocumentService.API/"] -COPY ["DocumentService/DocumentService.csproj", "DocumentService/"] +COPY ["OsmoDoc.API/OsmoDoc.API.csproj", "OsmoDoc.API/"] +COPY ["OsmoDoc/OsmoDoc.csproj", "OsmoDoc/"] # Restore the project dependencies -RUN dotnet restore "./DocumentService.API/./DocumentService.API.csproj" -RUN dotnet restore "./DocumentService/./DocumentService.csproj" +RUN dotnet restore "./OsmoDoc.API/./OsmoDoc.API.csproj" +RUN dotnet restore "./OsmoDoc/./OsmoDoc.csproj" # Copy the rest of the data COPY . . -WORKDIR "/app/DocumentService.API" +WORKDIR "/app/OsmoDoc.API" # Build the project and store artifacts in /out folder -RUN dotnet publish "./DocumentService.API.csproj" -c BUILD_CONFIGURATION -o /app/out +RUN dotnet publish "./OsmoDoc.API.csproj" -c BUILD_CONFIGURATION -o /app/out # Use the official ASP.NET runtime image as the base image FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base @@ -45,4 +45,4 @@ RUN chmod 755 /usr/bin/wkhtmltopdf RUN npm install -g --only=prod ejs # Set the entry point for the container -ENTRYPOINT ["dotnet", "DocumentService.API.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "OsmoDoc.API.dll"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 8df18d6..957757b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,10 +1,10 @@ services: - document-service: + osmodoc: build: context: . dockerfile: Dockerfile - image: document-service-docker - container_name: document-service-api + image: osmodoc-docker + container_name: osmodoc-api env_file: - .env ports: From 570d94f69446dc26073ecaae8fa46ba502eb8a63 Mon Sep 17 00:00:00 2001 From: Jatin Date: Sun, 15 Jun 2025 15:33:08 +0530 Subject: [PATCH 05/25] refactor: update docs directory for OsmoDoc rename --- ...l => OsmoDoc.Word.Models.ContentData.html} | 30 +-- ...l => OsmoDoc.Word.Models.ContentType.html} | 14 +- ... => OsmoDoc.Word.Models.DocumentData.html} | 22 +- ...ml => OsmoDoc.Word.Models.ParentBody.html} | 14 +- ...tml => OsmoDoc.Word.Models.TableData.html} | 18 +- ...d.Models.html => OsmoDoc.Word.Models.html} | 18 +- ...> OsmoDoc.Word.WordDocumentGenerator.html} | 16 +- ...mentService.Word.html => OsmoDocWord.html} | 10 +- docs/site/10.0.2/api/toc.html | 16 +- docs/site/manifest.json | 34 +-- docs/site/xrefmap.yml | 250 +++++++++--------- 11 files changed, 221 insertions(+), 221 deletions(-) rename docs/site/10.0.2/api/{DocumentService.Word.Models.ContentData.html => OsmoDoc.Word.Models.ContentData.html} (81%) rename docs/site/10.0.2/api/{DocumentService.Word.Models.ContentType.html => OsmoDoc.Word.Models.ContentType.html} (86%) rename docs/site/10.0.2/api/{DocumentService.Word.Models.DocumentData.html => OsmoDoc.Word.Models.DocumentData.html} (84%) rename docs/site/10.0.2/api/{DocumentService.Word.Models.ParentBody.html => OsmoDoc.Word.Models.ParentBody.html} (86%) rename docs/site/10.0.2/api/{DocumentService.Word.Models.TableData.html => OsmoDoc.Word.Models.TableData.html} (87%) rename docs/site/10.0.2/api/{DocumentService.Word.Models.html => OsmoDoc.Word.Models.html} (84%) rename docs/site/10.0.2/api/{DocumentService.Word.WordDocumentGenerator.html => OsmoDoc.Word.WordDocumentGenerator.html} (85%) rename docs/site/10.0.2/api/{DocumentService.Word.html => OsmoDocWord.html} (91%) diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.ContentData.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentData.html similarity index 81% rename from docs/site/10.0.2/api/DocumentService.Word.Models.ContentData.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentData.html index 240969e..c7f4db0 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.ContentData.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentData.html @@ -67,11 +67,11 @@
-
+
-

Class ContentData +

Class ContentData

Represents the data for a content placeholder in a Word document.
@@ -104,16 +104,16 @@
Inherited Members
object.MemberwiseClone()
-
Namespace: DocumentService.Word.Models
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word.Models
+
Assembly: OsmoDoc.dll
+
Syntax
public class ContentData

Properties

- -

Content

+ +

Content

Gets or sets the content to replace the placeholder with.
Declaration
@@ -135,8 +135,8 @@
Property Value
- -

ContentType

+ +

ContentType

Gets or sets the content type of the placeholder (text or image).
Declaration
@@ -153,13 +153,13 @@
Property Value
- ContentType + ContentType - -

ParentBody

+ +

ParentBody

Gets or sets the parent body of the placeholder (none or table).
Declaration
@@ -176,13 +176,13 @@
Property Value
- ParentBody + ParentBody - -

Placeholder

+ +

Placeholder

Gets or sets the placeholder name.
Declaration
diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.ContentType.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentType.html similarity index 86% rename from docs/site/10.0.2/api/DocumentService.Word.Models.ContentType.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentType.html index 976b450..53b1147 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.ContentType.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ContentType.html @@ -67,18 +67,18 @@
-
+
-

Enum ContentType +

Enum ContentType

Represents the content type of a placeholder in a Word document.
-
Namespace: DocumentService.Word.Models
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word.Models
+
Assembly: OsmoDoc.dll
+
Syntax
public enum ContentType
@@ -93,11 +93,11 @@

Fields - Image + Image The placeholder represents an image. - Text + Text The placeholder represents text content. diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.DocumentData.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.DocumentData.html similarity index 84% rename from docs/site/10.0.2/api/DocumentService.Word.Models.DocumentData.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.DocumentData.html index 22bee61..e7bde9c 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.DocumentData.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.DocumentData.html @@ -67,11 +67,11 @@

-
+
-

Class DocumentData +

Class DocumentData

Represents the data for a Word document, including content placeholders and table data.
@@ -104,16 +104,16 @@
Inherited Members
object.MemberwiseClone()
-
Namespace: DocumentService.Word.Models
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word.Models
+
Assembly: OsmoDoc.dll
+
Syntax
public class DocumentData

Properties

- -

Placeholders

+ +

Placeholders

Gets or sets the list of content placeholders in the document.
Declaration
@@ -130,13 +130,13 @@
Property Value
- List<ContentData> + List<ContentData> - -

TablesData

+ +

TablesData

Gets or sets the list of table data in the document.
Declaration
@@ -153,7 +153,7 @@
Property Value
- List<TableData> + List<TableData> diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.ParentBody.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ParentBody.html similarity index 86% rename from docs/site/10.0.2/api/DocumentService.Word.Models.ParentBody.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.ParentBody.html index 30027df..89ec062 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.ParentBody.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.ParentBody.html @@ -67,18 +67,18 @@
-
+
-

Enum ParentBody +

Enum ParentBody

Represents the parent body of a placeholder in a Word document.
-
Namespace: DocumentService.Word.Models
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word.Models
+
Assembly: OsmoDoc.dll
+
Syntax
public enum ParentBody
@@ -93,11 +93,11 @@

Fields - None + None The placeholder does not have a parent body. - Table + Table The placeholder belongs to a table. diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.TableData.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.TableData.html similarity index 87% rename from docs/site/10.0.2/api/DocumentService.Word.Models.TableData.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.TableData.html index 2f6f1bf..8a48600 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.TableData.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.TableData.html @@ -67,11 +67,11 @@

-
+
-

Class TableData +

Class TableData

Represents the data for a table in a Word document.
@@ -104,16 +104,16 @@
Inherited Members
object.MemberwiseClone()
-
Namespace: DocumentService.Word.Models
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word.Models
+
Assembly: OsmoDoc.dll
+
Syntax
public class TableData

Properties

- -

Data

+ +

Data

Gets or sets the list of dictionaries representing the data for each row in the table. Each dictionary contains column header-value pairs.
@@ -136,8 +136,8 @@
Property Value
- -

TablePos

+ +

TablePos

Gets or sets the position of the table in the document.
Declaration
diff --git a/docs/site/10.0.2/api/DocumentService.Word.Models.html b/docs/site/10.0.2/api/OsmoDoc.Word.Models.html similarity index 84% rename from docs/site/10.0.2/api/DocumentService.Word.Models.html rename to docs/site/10.0.2/api/OsmoDoc.Word.Models.html index bdb06e8..7d574b8 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.Models.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.Models.html @@ -5,10 +5,10 @@ - Namespace DocumentService.Word.Models + <title>Namespace OsmoDoc.Word.Models | Some Documentation - @@ -67,9 +67,9 @@
-
+
-

Namespace DocumentService.Word.Models +

Namespace OsmoDoc.Word.Models

@@ -77,18 +77,18 @@

Classes

-

ContentData

+

ContentData

Represents the data for a content placeholder in a Word document.
-

DocumentData

+

DocumentData

Represents the data for a Word document, including content placeholders and table data.
-

TableData

+

TableData

Represents the data for a table in a Word document.

Enums

-

ContentType

+

ContentType

Represents the content type of a placeholder in a Word document.
-

ParentBody

+

ParentBody

Represents the parent body of a placeholder in a Word document.
diff --git a/docs/site/10.0.2/api/DocumentService.Word.WordDocumentGenerator.html b/docs/site/10.0.2/api/OsmoDoc.Word.WordDocumentGenerator.html similarity index 85% rename from docs/site/10.0.2/api/DocumentService.Word.WordDocumentGenerator.html rename to docs/site/10.0.2/api/OsmoDoc.Word.WordDocumentGenerator.html index 68e403b..3bf7445 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.WordDocumentGenerator.html +++ b/docs/site/10.0.2/api/OsmoDoc.Word.WordDocumentGenerator.html @@ -67,11 +67,11 @@
-
+
-

Class WordDocumentGenerator +

Class WordDocumentGenerator

Provides functionality to generate Word documents based on templates and data.
@@ -104,16 +104,16 @@
Inherited Members
object.MemberwiseClone()
-
Namespace: DocumentService.Word
-
Assembly: DocumentService.dll
-
Syntax
+
Namespace: OsmoDoc.Word
+
Assembly: OsmoDoc.dll
+
Syntax
public static class WordDocumentGenerator

Methods

- -

GenerateDocumentByTemplate(string, DocumentData, string)

+ +

GenerateDocumentByTemplate(string, DocumentData, string)

Generates a Word document based on a template, replaces placeholders with data, and saves it to the specified output file path.
Declaration
@@ -136,7 +136,7 @@
Parameters
The file path of the template document. - DocumentData + DocumentData documentData The data to replace the placeholders in the template. diff --git a/docs/site/10.0.2/api/DocumentService.Word.html b/docs/site/10.0.2/api/OsmoDocWord.html similarity index 91% rename from docs/site/10.0.2/api/DocumentService.Word.html rename to docs/site/10.0.2/api/OsmoDocWord.html index b4b7697..2e1c7cb 100644 --- a/docs/site/10.0.2/api/DocumentService.Word.html +++ b/docs/site/10.0.2/api/OsmoDocWord.html @@ -5,10 +5,10 @@ - Namespace DocumentService.Word + <title>Namespace OsmoDoc.Word | Some Documentation - @@ -67,9 +67,9 @@
-
+
-

Namespace DocumentService.Word +

Namespace OsmoDoc.Word

@@ -77,7 +77,7 @@

Classes

-

WordDocumentGenerator

+

WordDocumentGenerator

Provides functionality to generate Word documents based on templates and data.
diff --git a/docs/site/10.0.2/api/toc.html b/docs/site/10.0.2/api/toc.html index 005aae4..74aad52 100644 --- a/docs/site/10.0.2/api/toc.html +++ b/docs/site/10.0.2/api/toc.html @@ -14,33 +14,33 @@
public static class WordDocumentGenerator { + private static readonly HttpClient _httpClient = new HttpClient(); + /// /// Generates a Word document based on a template, replaces placeholders with data, and saves it to the specified output file path. /// /// The file path of the template document. /// The data to replace the placeholders in the template. /// The file path to save the generated document. - public static void GenerateDocumentByTemplate(string templateFilePath, DocumentData documentData, string outputFilePath) + public async static Task GenerateDocumentByTemplate(string templateFilePath, DocumentData documentData, string outputFilePath) { try { @@ -41,22 +45,22 @@ public static void GenerateDocumentByTemplate(string templateFilePath, DocumentD if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Text) { string placeholder = "{" + content.Placeholder + "}"; - textPlaceholders.Add(placeholder, content.Content); + textPlaceholders.TryAdd(placeholder, content.Content); } else if (content.ParentBody == ParentBody.None && content.ContentType == ContentType.Image) { string placeholder = content.Placeholder; - imagePlaceholders.Add(placeholder, content.Content); + imagePlaceholders.TryAdd(placeholder, content.Content); } else if (content.ParentBody == ParentBody.Table && content.ContentType == ContentType.Text) { string placeholder = "{" + content.Placeholder + "}"; - tableContentPlaceholders.Add(placeholder, content.Content); + tableContentPlaceholders.TryAdd(placeholder, content.Content); } } // Create document of the template - XWPFDocument document = GetXWPFDocument(templateFilePath); + XWPFDocument document = await GetXWPFDocument(templateFilePath); // For each element in the document foreach (IBodyElement element in document.BodyElements) @@ -95,7 +99,7 @@ public static void GenerateDocumentByTemplate(string templateFilePath, DocumentD } // Write the document to output file path and close the document - WriteDocument(document, outputFilePath); + await WriteDocument(document, outputFilePath); document.Close(); /* @@ -105,9 +109,9 @@ public static void GenerateDocumentByTemplate(string templateFilePath, DocumentD * Since both the packages have different execution method, so they are handled separately */ // Replace all the image placeholders in the output file - ReplaceImagePlaceholders(outputFilePath, outputFilePath, imagePlaceholders); + await ReplaceImagePlaceholders(outputFilePath, outputFilePath, imagePlaceholders); } - catch (Exception ex) + catch (Exception) { throw; } @@ -118,12 +122,16 @@ public static void GenerateDocumentByTemplate(string templateFilePath, DocumentD ///
/// The file path of the Word document. /// An instance of XWPFDocument representing the Word document. - private static XWPFDocument GetXWPFDocument(string docFilePath) + private async static Task GetXWPFDocument(string docFilePath) { - FileStream readStream = File.OpenRead(docFilePath); - XWPFDocument document = new XWPFDocument(readStream); - readStream.Close(); - return document; + return await Task.Run(() => + { + using (FileStream readStream = File.OpenRead(docFilePath)) + { + XWPFDocument document = new XWPFDocument(readStream); + return document; + } + }); } /// @@ -131,12 +139,15 @@ private static XWPFDocument GetXWPFDocument(string docFilePath) /// /// The XWPFDocument to write. /// The file path to save the document. - private static void WriteDocument(XWPFDocument document, string filePath) + private async static Task WriteDocument(XWPFDocument document, string filePath) { - using (FileStream writeStream = File.Create(filePath)) + await Task.Run(() => { - document.Write(writeStream); - } + using (FileStream writeStream = File.Create(filePath)) + { + document.Write(writeStream); + } + }); } /// @@ -251,59 +262,65 @@ private static XWPFTable PopulateTable(XWPFTable table, TableData tableData) /// The input file path containing the image placeholders. /// The output file path where the updated document will be saved. /// The dictionary of image placeholders and their corresponding image paths. - private static void ReplaceImagePlaceholders(string inputFilePath, string outputFilePath, Dictionary imagePlaceholders) + private async static Task ReplaceImagePlaceholders(string inputFilePath, string outputFilePath, Dictionary imagePlaceholders) { - byte[] docBytes = File.ReadAllBytes(inputFilePath); - - // Write document bytes to memory - MemoryStream memoryStream = new MemoryStream(); - memoryStream.Write(docBytes, 0, docBytes.Length); + byte[] docBytes = await File.ReadAllBytesAsync(inputFilePath); - using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(memoryStream, true)) + // Write document bytes to memory asynchronously + using (MemoryStream memoryStream = new MemoryStream()) { - MainDocumentPart mainDocumentPart = wordDocument.MainDocumentPart; + await memoryStream.WriteAsync(docBytes, 0, docBytes.Length); + memoryStream.Position = 0; - // Get a list of drawings (images) - IEnumerable drawings = mainDocumentPart.Document.Descendants().ToList(); - - /* - * FIXME: Look on how we can improve this loop operation. - */ - foreach (Drawing drawing in drawings) + using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(memoryStream, true)) { - DocProperties docProperty = drawing.Descendants().FirstOrDefault(); + MainDocumentPart? mainDocumentPart = wordDocument.MainDocumentPart; - // If drawing / image name is present in imagePlaceholders dictionary, then replace image - if (docProperty != null && imagePlaceholders.ContainsKey(docProperty.Name)) + // Get a list of drawings (images) + IEnumerable drawings = new List(); + if (mainDocumentPart != null) { - List drawingBlips = drawing.Descendants().ToList(); + drawings = mainDocumentPart.Document.Descendants().ToList(); + } + + /* + * FIXME: Look on how we can improve this loop operation. + */ + foreach (Drawing drawing in drawings) + { + DocProperties? docProperty = drawing.Descendants().FirstOrDefault(); - foreach (Blip blip in drawingBlips) + // If drawing / image name is present in imagePlaceholders dictionary, then replace image + if (docProperty != null && imagePlaceholders.ContainsKey(docProperty.Name)) { - OpenXmlPart imagePart = wordDocument.MainDocumentPart.GetPartById(blip.Embed); + List drawingBlips = drawing.Descendants().ToList(); - using (BinaryWriter writer = new BinaryWriter(imagePart.GetStream())) + foreach (Blip blip in drawingBlips) { + OpenXmlPart imagePart = wordDocument.MainDocumentPart.GetPartById(blip.Embed); + string imagePath = imagePlaceholders[docProperty.Name]; - /* - * WebClient has been deprecated and we need to use HTTPClient. - * This involves the methods to be asynchronous. - */ - using (WebClient webClient = new WebClient()) + // Asynchronously download image data using HttpClient + byte[] imageData = await _httpClient.GetByteArrayAsync(imagePath); + + using (Stream partStream = imagePart.GetStream(FileMode.OpenOrCreate, FileAccess.Write)) { - writer.Write(webClient.DownloadData(imagePath)); + // Asynchronously write image data to the part stream + await partStream.WriteAsync(imageData, 0, imageData.Length); + partStream.SetLength(imageData.Length); // Ensure the stream is truncated if new data is smaller } } } } } + // Overwrite the output file asynchronously + using (FileStream fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write)) + { + // Reset MemoryStream position before writing to fileStream + memoryStream.Position = 0; + await memoryStream.CopyToAsync(fileStream); + } } - - // Overwrite the output file - FileStream fileStream = new FileStream(outputFilePath, FileMode.OpenOrCreate, FileAccess.ReadWrite); - memoryStream.WriteTo(fileStream); - fileStream.Close(); - memoryStream.Close(); } } \ No newline at end of file From 548252ea403d42ac581b559c19be363359effab8 Mon Sep 17 00:00:00 2001 From: Jatin Date: Wed, 18 Jun 2025 20:05:51 +0530 Subject: [PATCH 18/25] refactor: remove using block in GetXWPFDocument function --- OsmoDoc/Word/WordDocumentGenerator.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/OsmoDoc/Word/WordDocumentGenerator.cs b/OsmoDoc/Word/WordDocumentGenerator.cs index f4b12aa..f270105 100644 --- a/OsmoDoc/Word/WordDocumentGenerator.cs +++ b/OsmoDoc/Word/WordDocumentGenerator.cs @@ -90,7 +90,7 @@ public async static Task GenerateDocumentByTemplate(string templateFilePath, Doc // Populate the table with data if it is passed in tablesData list foreach (TableData insertData in tablesData) { - if (insertData.TablePos <= document.Tables.Count && table == document.Tables[insertData.TablePos - 1]) + if (insertData.TablePos >= 1 && insertData.TablePos <= document.Tables.Count && table == document.Tables[insertData.TablePos - 1]) { table = PopulateTable(table, insertData); } @@ -126,11 +126,8 @@ private async static Task GetXWPFDocument(string docFilePath) { return await Task.Run(() => { - using (FileStream readStream = File.OpenRead(docFilePath)) - { - XWPFDocument document = new XWPFDocument(readStream); - return document; - } + FileStream readStream = File.OpenRead(docFilePath); + return new XWPFDocument(readStream); }); } From d1892765918dedb9c05c49be95e9a4838ee1207d Mon Sep 17 00:00:00 2001 From: Jatin Date: Thu, 19 Jun 2025 09:11:45 +0530 Subject: [PATCH 19/25] feat: add null checks for ejsData and function parameters --- OsmoDoc/Pdf/PdfDocumentGenerator.cs | 20 ++++++++++++++++++-- OsmoDoc/Word/WordDocumentGenerator.cs | 17 +++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/OsmoDoc/Pdf/PdfDocumentGenerator.cs b/OsmoDoc/Pdf/PdfDocumentGenerator.cs index 0455a77..22f6fe0 100644 --- a/OsmoDoc/Pdf/PdfDocumentGenerator.cs +++ b/OsmoDoc/Pdf/PdfDocumentGenerator.cs @@ -24,6 +24,21 @@ public async static Task GeneratePdf(string templatePath, List { try { + if (metaDataList is null) + { + throw new ArgumentNullException(nameof(metaDataList)); + } + + if (string.IsNullOrWhiteSpace(templatePath)) + { + throw new ArgumentNullException(nameof(templatePath)); + } + + if (string.IsNullOrWhiteSpace(outputFilePath)) + { + throw new ArgumentNullException(nameof(outputFilePath)); + } + if (string.IsNullOrWhiteSpace(OsmoDocPdfConfig.WkhtmltopdfPath) && !RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { throw new Exception("WkhtmltopdfPath is not set in OsmoDocPdfConfig."); @@ -180,7 +195,8 @@ private async static Task ConvertEjsToHTML(string ejsFilePath, string ou // Write json data string to json file string ejsDataJsonFilePath = Path.Combine(tempDirectoryFilePath, "ejsData.json"); - File.WriteAllText(ejsDataJsonFilePath, ejsDataJson); + string contentToWrite = ejsDataJson ?? "{}"; + File.WriteAllText(ejsDataJsonFilePath, contentToWrite); string commandLine = "cmd.exe"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) @@ -243,7 +259,7 @@ private static string EjsToHtmlArgumentsBasedOnOS(string ejsFilePath, string ejs } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - return $"{ejsFilePath} -f {ejsDataJsonFilePath} -o {tempHtmlFilePath}"; + return $"\"{ejsFilePath}\" -f \"{ejsDataJsonFilePath}\" -o \"{tempHtmlFilePath}\""; } else { diff --git a/OsmoDoc/Word/WordDocumentGenerator.cs b/OsmoDoc/Word/WordDocumentGenerator.cs index f270105..910d13c 100644 --- a/OsmoDoc/Word/WordDocumentGenerator.cs +++ b/OsmoDoc/Word/WordDocumentGenerator.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; +using IOPath = System.IO.Path; using System.Linq; using System.Net; using System.Text.RegularExpressions; @@ -32,6 +33,16 @@ public async static Task GenerateDocumentByTemplate(string templateFilePath, Doc { try { + if (string.IsNullOrWhiteSpace(templateFilePath)) + { + throw new ArgumentNullException(nameof(templateFilePath)); + } + + if (string.IsNullOrWhiteSpace(outputFilePath)) + { + throw new ArgumentNullException(nameof(outputFilePath)); + } + List contentData = documentData.Placeholders; List tablesData = documentData.TablesData; @@ -138,6 +149,12 @@ private async static Task GetXWPFDocument(string docFilePath) /// The file path to save the document. private async static Task WriteDocument(XWPFDocument document, string filePath) { + string? directory = IOPath.GetDirectoryName(filePath); + if (!string.IsNullOrWhiteSpace(directory)) + { + Directory.CreateDirectory(directory); + } + await Task.Run(() => { using (FileStream writeStream = File.Create(filePath)) From 325426ac5af51f6a2c022d303e1cce819a660fb7 Mon Sep 17 00:00:00 2001 From: Jatin Date: Thu, 19 Jun 2025 09:53:13 +0530 Subject: [PATCH 20/25] feat: avoid temporary path collisions under concurrent requests by adding unique id in the filename --- OsmoDoc/Pdf/PdfDocumentGenerator.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OsmoDoc/Pdf/PdfDocumentGenerator.cs b/OsmoDoc/Pdf/PdfDocumentGenerator.cs index 22f6fe0..a045d16 100644 --- a/OsmoDoc/Pdf/PdfDocumentGenerator.cs +++ b/OsmoDoc/Pdf/PdfDocumentGenerator.cs @@ -95,7 +95,8 @@ private static string ReplaceFileElementsWithMetaData(string templatePath, List< { throw new Exception($"No directory found for the path: {outputFilePath}"); } - string tempHtmlFilePath = Path.Combine(directoryPath, "Modified"); + string uniqueId = Guid.NewGuid().ToString("N"); + string tempHtmlFilePath = Path.Combine(directoryPath, $"Modified_{uniqueId}"); string tempHtmlFile = Path.Combine(tempHtmlFilePath, "modifiedHtml.html"); if (!Directory.Exists(tempHtmlFilePath)) @@ -177,7 +178,8 @@ private async static Task ConvertEjsToHTML(string ejsFilePath, string ou { throw new Exception($"No directory found for the path: {outputFilePath}"); } - string tempDirectoryFilePath = Path.Combine(directoryPath, "Temp"); + string uniqueId = Guid.NewGuid().ToString("N"); + string tempDirectoryFilePath = Path.Combine(directoryPath, $"Temp_{uniqueId}"); if (!Directory.Exists(tempDirectoryFilePath)) { From 4950f3ecb9c23b1c457371da3536ace85e583f08 Mon Sep 17 00:00:00 2001 From: Jatin Date: Thu, 19 Jun 2025 10:04:25 +0530 Subject: [PATCH 21/25] refactor: initialize PDF tool path once at startup --- OsmoDoc.API/Controllers/PdfController.cs | 12 ------------ OsmoDoc.API/Program.cs | 7 +++++++ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/OsmoDoc.API/Controllers/PdfController.cs b/OsmoDoc.API/Controllers/PdfController.cs index 987d9c6..2e11b2a 100644 --- a/OsmoDoc.API/Controllers/PdfController.cs +++ b/OsmoDoc.API/Controllers/PdfController.cs @@ -44,12 +44,6 @@ public async Task> GeneratePdf(PdfGenerationRequestDT // Save base64 html template to inputs directory await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, htmlTemplateFilePath, this._configuration); - // Initialize tools and output filepaths - OsmoDocPdfConfig.WkhtmltopdfPath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value - ); - string outputFilePath = Path.Combine( this._hostingEnvironment.WebRootPath, this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, @@ -135,12 +129,6 @@ public async Task> GeneratePdfUsingEjs(PdfGenerationR // Save base64 html template to inputs directory await Base64StringHelper.SaveBase64StringToFilePath(request.Base64, ejsTemplateFilePath, this._configuration); - // Initialize tools and output filepaths - OsmoDocPdfConfig.WkhtmltopdfPath = Path.Combine( - this._hostingEnvironment.WebRootPath, - this._configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value - ); - string outputFilePath = Path.Combine( this._hostingEnvironment.WebRootPath, this._configuration.GetSection("TEMPORARY_FILE_PATHS:TEMP").Value, diff --git a/OsmoDoc.API/Program.cs b/OsmoDoc.API/Program.cs index bf4266b..3636ece 100644 --- a/OsmoDoc.API/Program.cs +++ b/OsmoDoc.API/Program.cs @@ -9,6 +9,7 @@ using Microsoft.IdentityModel.Tokens; using System.Text; using Swashbuckle.AspNetCore.Filters; +using OsmoDoc.Pdf; WebApplicationBuilder builder = WebApplication.CreateBuilder(args); @@ -25,6 +26,12 @@ string dotenv = Path.GetFullPath(Path.Combine(root, "..", ".env")); OsmoDoc.API.DotEnv.Load(dotenv); +// Initialize PDF tool path once at startup +OsmoDocPdfConfig.WkhtmltopdfPath = Path.Combine( + builder.Environment.WebRootPath, + builder.Configuration.GetSection("STATIC_FILE_PATHS:HTML_TO_PDF_TOOL").Value! +); + // Configure request size limit long requestBodySizeLimitBytes = Convert.ToInt64(builder.Configuration.GetSection("CONFIG:REQUEST_BODY_SIZE_LIMIT_BYTES").Value); From 3f786502841280ac5f3601764a5e3e9066a193cd Mon Sep 17 00:00:00 2001 From: Jatin Date: Thu, 19 Jun 2025 13:22:28 +0530 Subject: [PATCH 22/25] feat: handle row cell-count mismatch when populating tables --- OsmoDoc/Word/WordDocumentGenerator.cs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/OsmoDoc/Word/WordDocumentGenerator.cs b/OsmoDoc/Word/WordDocumentGenerator.cs index 910d13c..5e58b62 100644 --- a/OsmoDoc/Word/WordDocumentGenerator.cs +++ b/OsmoDoc/Word/WordDocumentGenerator.cs @@ -248,18 +248,20 @@ private static XWPFTable PopulateTable(XWPFTable table, TableData tableData) // For each row's data stored in table data foreach (Dictionary rowData in tableData.Data) { - // Create a new row and its columns - XWPFTableRow row = table.CreateRow(); - - // For each cell in row - for (int cellNumber = 0; cellNumber < row.GetTableCells().Count; cellNumber++) + XWPFTableRow row = table.CreateRow(); // This is a DATA row, not header + + int columnCount = headerRow.GetTableCells().Count; // Read from header + for (int cellNumber = 0; cellNumber < columnCount; cellNumber++) { - XWPFTableCell cell = row.GetCell(cellNumber); + // Ensure THIS data row has enough cells + while (row.GetTableCells().Count <= cellNumber) + { + row.AddNewTableCell(); + } - // Get the column header of this cell + // Now populate the cell in this data row + XWPFTableCell cell = row.GetCell(cellNumber); string columnHeader = headerRow.GetCell(cellNumber).GetText(); - - // Add the cell's value if (rowData.ContainsKey(columnHeader)) { cell.SetText(rowData[columnHeader]); From 5f0e3bf4967ae7096c1a6d097eddfc704f55df90 Mon Sep 17 00:00:00 2001 From: Jatin Date: Fri, 20 Jun 2025 05:01:41 +0530 Subject: [PATCH 23/25] feat: avoid Task.Run for I/O operations and fix resource disposal --- OsmoDoc/Word/WordDocumentGenerator.cs | 30 +++++++++++---------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/OsmoDoc/Word/WordDocumentGenerator.cs b/OsmoDoc/Word/WordDocumentGenerator.cs index 5e58b62..49c1588 100644 --- a/OsmoDoc/Word/WordDocumentGenerator.cs +++ b/OsmoDoc/Word/WordDocumentGenerator.cs @@ -20,9 +20,7 @@ namespace OsmoDoc.Word; /// Provides functionality to generate Word documents based on templates and data. /// public static class WordDocumentGenerator -{ - private static readonly HttpClient _httpClient = new HttpClient(); - +{ /// /// Generates a Word document based on a template, replaces placeholders with data, and saves it to the specified output file path. /// @@ -110,7 +108,7 @@ public async static Task GenerateDocumentByTemplate(string templateFilePath, Doc } // Write the document to output file path and close the document - await WriteDocument(document, outputFilePath); + WriteDocument(document, outputFilePath); document.Close(); /* @@ -135,11 +133,9 @@ public async static Task GenerateDocumentByTemplate(string templateFilePath, Doc /// An instance of XWPFDocument representing the Word document. private async static Task GetXWPFDocument(string docFilePath) { - return await Task.Run(() => - { - FileStream readStream = File.OpenRead(docFilePath); - return new XWPFDocument(readStream); - }); + byte[] fileBytes = await File.ReadAllBytesAsync(docFilePath); + using MemoryStream memoryStream = new MemoryStream(fileBytes); + return new XWPFDocument(memoryStream); } /// @@ -147,21 +143,18 @@ private async static Task GetXWPFDocument(string docFilePath) /// /// The XWPFDocument to write. /// The file path to save the document. - private async static Task WriteDocument(XWPFDocument document, string filePath) + private static void WriteDocument(XWPFDocument document, string filePath) { string? directory = IOPath.GetDirectoryName(filePath); if (!string.IsNullOrWhiteSpace(directory)) { Directory.CreateDirectory(directory); } - - await Task.Run(() => + + using (FileStream writeStream = File.Create(filePath)) { - using (FileStream writeStream = File.Create(filePath)) - { - document.Write(writeStream); - } - }); + document.Write(writeStream); + } } /// @@ -318,7 +311,8 @@ private async static Task ReplaceImagePlaceholders(string inputFilePath, string string imagePath = imagePlaceholders[docProperty.Name]; // Asynchronously download image data using HttpClient - byte[] imageData = await _httpClient.GetByteArrayAsync(imagePath); + using HttpClient httpClient = new HttpClient(); + byte[] imageData = await httpClient.GetByteArrayAsync(imagePath); using (Stream partStream = imagePart.GetStream(FileMode.OpenOrCreate, FileAccess.Write)) { From 4036fb7ed8aa8b4c31305e9a43272555d6bcd0af Mon Sep 17 00:00:00 2001 From: Jatin Date: Wed, 18 Jun 2025 11:07:35 +0530 Subject: [PATCH 24/25] docs: update example env --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 50a5a4e..8bbfe3b 100644 --- a/.env.example +++ b/.env.example @@ -1 +1 @@ -JWT_KEY=xxx \ No newline at end of file +JWT_KEY=PLACEHOLDER_REPLACE_WITH_STRONG_KEY_MIN_32_CHARS_BEFORE_USE \ No newline at end of file From 1a4dcddc5173c677ca2371970ffca081813bc442 Mon Sep 17 00:00:00 2001 From: Jatin Date: Thu, 19 Jun 2025 15:06:32 +0530 Subject: [PATCH 25/25] feat: add login endpoint for generating jwt token --- OsmoDoc.API/Controllers/LoginController.cs | 43 ++++++++++++++++++++++ OsmoDoc.API/Models/LoginRequestDTO.cs | 10 +++++ 2 files changed, 53 insertions(+) create mode 100644 OsmoDoc.API/Controllers/LoginController.cs create mode 100644 OsmoDoc.API/Models/LoginRequestDTO.cs diff --git a/OsmoDoc.API/Controllers/LoginController.cs b/OsmoDoc.API/Controllers/LoginController.cs new file mode 100644 index 0000000..b592bb2 --- /dev/null +++ b/OsmoDoc.API/Controllers/LoginController.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using OsmoDoc.API.Models; +using OsmoDoc.API.Helpers; +using Microsoft.AspNetCore.Authorization; + +namespace OsmoDoc.API.Controllers; + +[Route("api")] +[ApiController] +public class LoginController : ControllerBase +{ + private readonly ILogger _logger; + + public LoginController(ILogger logger) + { + this._logger = logger; + } + + [HttpPost] + [Route("login")] + [AllowAnonymous] + public ActionResult Login([FromBody] LoginRequestDTO loginRequest) + { + BaseResponse response = new BaseResponse(ResponseStatus.Fail); + try + { + string token = AuthenticationHelper.JwtTokenGenerator(loginRequest.Email); + + response.Status = ResponseStatus.Success; + response.AuthToken = token; + response.Message = "Token generated successfully"; + return this.Ok(response); + } + catch (Exception ex) + { + response.Status = ResponseStatus.Error; + response.Message = ex.Message; + this._logger.LogError(ex.Message); + this._logger.LogError(ex.StackTrace); + return this.StatusCode(StatusCodes.Status500InternalServerError, response); + } + } +} \ No newline at end of file diff --git a/OsmoDoc.API/Models/LoginRequestDTO.cs b/OsmoDoc.API/Models/LoginRequestDTO.cs new file mode 100644 index 0000000..bf7f680 --- /dev/null +++ b/OsmoDoc.API/Models/LoginRequestDTO.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace OsmoDoc.API.Models; + +public class LoginRequestDTO +{ + [Required(ErrorMessage = "Email is required")] + [EmailAddress(ErrorMessage = "Invalid email format")] + public string Email { get; set; } = string.Empty; +} \ No newline at end of file