{"id":27455084,"url":"https://github.com/marcominerva/pdfsmith","last_synced_at":"2025-07-01T12:03:41.379Z","repository":{"id":286138067,"uuid":"960495881","full_name":"marcominerva/PdfSmith","owner":"marcominerva","description":"A powerful REST API service that generates PDF documents from dynamic HTML templates.","archived":false,"fork":false,"pushed_at":"2025-06-27T12:22:12.000Z","size":96,"stargazers_count":8,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-01T12:02:38.013Z","etag":null,"topics":["c-sharp","minimal-api","pdf","pdf-generation","pdf-generator","pdf-generator-api","service","template-engine"],"latest_commit_sha":null,"homepage":"https://pdfsmith.azurewebsites.net","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcominerva.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-04T14:32:28.000Z","updated_at":"2025-06-27T12:22:15.000Z","dependencies_parsed_at":"2025-05-02T15:24:24.193Z","dependency_job_id":"895fc95b-3538-48e6-a569-8e804f3132d2","html_url":"https://github.com/marcominerva/PdfSmith","commit_stats":null,"previous_names":["marcominerva/pdfsmith"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/marcominerva/PdfSmith","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcominerva%2FPdfSmith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcominerva%2FPdfSmith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcominerva%2FPdfSmith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcominerva%2FPdfSmith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcominerva","download_url":"https://codeload.github.com/marcominerva/PdfSmith/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcominerva%2FPdfSmith/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262959563,"owners_count":23391057,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c-sharp","minimal-api","pdf","pdf-generation","pdf-generator","pdf-generator-api","service","template-engine"],"created_at":"2025-04-15T15:16:34.372Z","updated_at":"2025-07-01T12:03:41.346Z","avatar_url":"https://github.com/marcominerva.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PDF Smith\n\nA powerful REST API service that generates PDF documents from dynamic HTML templates. PDF Smith supports multiple template engines, offers flexible PDF configuration options, and provides secure API key-based authentication with rate limiting.\n\n## 🚀 Features\n\n- **Dynamic PDF Generation**: Create PDFs from HTML templates with dynamic data injection\n- **Multiple Template Engines**: Support for both Scriban and Razor template engines\n- **Flexible PDF Options**: Configure page size, orientation, margins, and more\n- **API Key Authentication**: Secure access with subscription-based API keys\n- **Rate Limiting**: Configurable request limits per subscription\n- **Localization Support**: Multi-language template rendering\n- **OpenAPI Documentation**: Built-in Swagger documentation\n- **High Performance**: Built on .NET 9.0 with asynchronous operations\n\n## 📋 Table of Contents\n\n- [Installation](#️-installation)\n- [Authentication](#-authentication)\n- [API Reference](#-api-reference)\n- [Template Engines](#-template-engines)\n- [PDF Configuration](#-pdf-configuration)\n- [Usage Examples](#-usage-examples)\n- [Error Handling](#️-error-handling)\n- [Rate Limiting](#️-rate-limiting)\n- [Configuration](#️-configuration)\n\n## 🛠️ Installation\n\n### Prerequisites\n\n- .NET 9.0 SDK or later\n- Chromium browser (automatically installed via Playwright)\n\n### Setup\n\n1. Clone the repository:\n```bash\ngit clone https://github.com/marcominerva/PdfSmith.git\ncd PdfSmith\n```\n\n2. Build the solution:\n```bash\ndotnet build\n```\n\n3. Configure your database connection in `appsettings.json`:\n```json\n{\n  \"ConnectionStrings\": {\n    \"SqlConnection\": \"your-connection-string-here\"\n  }\n}\n```\n\n4. Run the application:\n```bash\ndotnet run --project src/PdfSmith\n```\n\nThe API will be available at `https://localhost:7226` (or your configured port).\n\n## 🔐 Authentication\n\nPdfSmith uses API key authentication with subscription-based access control.\n\n### API Key Setup\n\n1. Include your API key in the request header:\n```\nx-api-key: your-api-key-here\n```\n\n2. Each subscription has configurable rate limits:\n   - **Requests per window**: Number of allowed requests\n   - **Window duration**: Time window in minutes\n   - **Validity period**: API key expiration dates\n\n### Default Administrator Account\n\nA default administrator account is created automatically with the following configuration:\n- Username: Set via `AppSettings:AdministratorUserName`\n- API Key: Set via `AppSettings:AdministratorApiKey`\n- Default limits: 10 requests per minute\n\n## 📚 API Reference\n\n### Generate PDF\n\n**Endpoint:** `POST /api/pdf`\n\n**Headers:**\n- `x-api-key`: Your API key (required)\n- `Accept-Language`: Language preference (optional, e.g., \"en-US\", \"it-IT\")\n\n**Request Body:**\n```json\n{\n  \"template\": \"HTML template string\",\n  \"model\": {\n    \"key\": \"value\",\n    \"nested\": {\n      \"data\": \"structure\"\n    }\n  },\n  \"templateEngine\": \"razor\",\n  \"fileName\": \"document.pdf\",\n  \"options\": {\n    \"pageSize\": \"A4\",\n    \"orientation\": \"Portrait\",\n    \"margin\": {\n      \"top\": \"2.5cm\",\n      \"bottom\": \"2cm\",\n      \"left\": \"2cm\",\n      \"right\": \"2cm\"\n    }\n  }\n}\n```\n\n**Response:**\n- **Content-Type:** `application/pdf`\n- **Content-Disposition:** `attachment; filename=\"document.pdf\"`\n- **Body:** PDF file binary data\n\n## 🎨 Template Engines\n\nPdfSmith supports two powerful template engines:\n\n### Razor Template Engine\n\nRazor provides C#-based templating with full programming capabilities.\n\n**Key:** `\"razor\"`\n\n**Example:**\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n    \u003ch1\u003eHello @Model.Name!\u003c/h1\u003e\n    \u003cp\u003eOrder Date: @Model.Date.ToString(\"dd/MM/yyyy\")\u003c/p\u003e\n    \u003cul\u003e\n    @foreach(var item in Model.Items)\n    {\n        \u003cli\u003e@item.Name - @item.Price.ToString(\"C\")\u003c/li\u003e\n    }\n    \u003c/ul\u003e\n    \u003cp\u003eTotal: @Model.Total.ToString(\"C\")\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Scriban Template Engine\n\nScriban is a fast, powerful, safe, and lightweight text templating language.\n\n**Key:** `\"scriban\"`\n\n**Example:**\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n    \u003ch1\u003eHello {{ Model.Name }}!\u003c/h1\u003e\n    \u003cp\u003eOrder Date: {{ Model.Date | date.to_string '%d/%m/%Y' }}\u003c/p\u003e\n    \u003cul\u003e\n    {{- for item in Model.Items }}\n        \u003cli\u003e{{ item.Name }} - {{ item.Price | object.format \"C\" }}\u003c/li\u003e\n    {{- end }}\n    \u003c/ul\u003e\n    \u003cp\u003eTotal: {{ Model.Total | object.format \"C\" }}\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n## 📄 PDF Configuration\n\n### Page Size Options\n- Standard sizes: `\"A4\"`, `\"A3\"`, `\"A5\"`, `\"Letter\"`, `\"Legal\"`\n- Custom sizes: `\"210mm x 297mm\"` or `\"8.5in x 11in\"`\n\n### Orientation\n- `\"Portrait\"` (default)\n- `\"Landscape\"`\n\n### Margins\nConfigure margins using CSS units:\n```json\n{\n  \"margin\": {\n    \"top\": \"2.5cm\",\n    \"bottom\": \"2cm\", \n    \"left\": \"2cm\",\n    \"right\": \"2cm\"\n  }\n}\n```\n\nSupported units: `mm`, `cm`, `in`, `px`, `pt`, `pc`\n\n## 💡 Usage Examples\n\n### Basic PDF Generation\n\n```csharp\nusing System.Net.Http.Json;\nusing PdfSmith.Shared.Models;\n\nvar httpClient = new HttpClient();\nhttpClient.DefaultRequestHeaders.Add(\"x-api-key\", \"your-api-key\");\n\nvar request = new PdfGenerationRequest(\n    template: \"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eHello @Model.Name!\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\",\n    model: new { Name = \"John Doe\" },\n    templateEngine: \"razor\"\n);\n\nvar response = await httpClient.PostAsJsonAsync(\"https://localhost:7226/api/pdf\", request);\nvar pdfBytes = await response.Content.ReadAsByteArrayAsync();\nawait File.WriteAllBytesAsync(\"output.pdf\", pdfBytes);\n```\n\n### Advanced PDF with Custom Options\n\n```csharp\nvar request = new PdfGenerationRequest(\n    template: htmlTemplate,\n    model: orderData,\n    options: new PdfOptions\n    {\n        PageSize = \"A4\",\n        Orientation = PdfOrientation.Portrait,\n        Margin = new PdfMargin(\n            Top: \"50mm\",\n            Bottom: \"30mm\", \n            Left: \"25mm\",\n            Right: \"25mm\"\n        )\n    },\n    templateEngine: \"razor\",\n    fileName: \"invoice.pdf\"\n);\n```\n\n### Invoice Generation Example\n\n```csharp\nvar order = new\n{\n    CustomerName = \"Acme Corp\",\n    Date = DateTime.Now,\n    InvoiceNumber = \"INV-2024-001\",\n    Items = new[]\n    {\n        new { Name = \"Product A\", Quantity = 2, Price = 29.99m },\n        new { Name = \"Product B\", Quantity = 1, Price = 49.99m }\n    },\n    Total = 109.97m\n};\n\nvar htmlTemplate = \"\"\"\n\u003chtml\u003e\n\u003chead\u003e\n    \u003cstyle\u003e\n        body { font-family: Arial, sans-serif; }\n        .header { text-align: center; margin-bottom: 30px; }\n        .invoice-details { margin-bottom: 20px; }\n        table { width: 100%; border-collapse: collapse; }\n        th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }\n        .total { font-weight: bold; text-align: right; }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"header\"\u003e\n        \u003ch1\u003eInvoice\u003c/h1\u003e\n        \u003cp\u003eInvoice #@Model.InvoiceNumber\u003c/p\u003e\n    \u003c/div\u003e\n    \n    \u003cdiv class=\"invoice-details\"\u003e\n        \u003cp\u003e\u003cstrong\u003eCustomer:\u003c/strong\u003e @Model.CustomerName\u003c/p\u003e\n        \u003cp\u003e\u003cstrong\u003eDate:\u003c/strong\u003e @Model.Date.ToString(\"dd/MM/yyyy\")\u003c/p\u003e\n    \u003c/div\u003e\n    \n    \u003ctable\u003e\n        \u003cthead\u003e\n            \u003ctr\u003e\n                \u003cth\u003eItem\u003c/th\u003e\n                \u003cth\u003eQuantity\u003c/th\u003e\n                \u003cth\u003ePrice\u003c/th\u003e\n                \u003cth\u003eTotal\u003c/th\u003e\n            \u003c/tr\u003e\n        \u003c/thead\u003e\n        \u003ctbody\u003e\n            @foreach(var item in Model.Items)\n            {\n            \u003ctr\u003e\n                \u003ctd\u003e@item.Name\u003c/td\u003e\n                \u003ctd\u003e@item.Quantity\u003c/td\u003e\n                \u003ctd\u003e@item.Price.ToString(\"C\")\u003c/td\u003e\n                \u003ctd\u003e@((item.Quantity * item.Price).ToString(\"C\"))\u003c/td\u003e\n            \u003c/tr\u003e\n            }\n        \u003c/tbody\u003e\n        \u003ctfoot\u003e\n            \u003ctr\u003e\n                \u003ctd colspan=\"3\" class=\"total\"\u003eTotal:\u003c/td\u003e\n                \u003ctd class=\"total\"\u003e@Model.Total.ToString(\"C\")\u003c/td\u003e\n            \u003c/tr\u003e\n        \u003c/tfoot\u003e\n    \u003c/table\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n\"\"\";\n\nvar request = new PdfGenerationRequest(htmlTemplate, order, templateEngine: \"razor\");\n```\n\n## ⚠️ Error Handling\n\nThe API returns appropriate HTTP status codes and error details:\n\n### Common Status Codes\n\n- **200 OK**: PDF generated successfully\n- **400 Bad Request**: Invalid request data or template errors\n- **401 Unauthorized**: Invalid or missing API key\n- **408 Request Timeout**: Generation took longer than 30 seconds\n- **429 Too Many Requests**: Rate limit exceeded\n- **500 Internal Server Error**: Unexpected server error\n\n### Error Response Format\n\n```json\n{\n  \"type\": \"https://httpstatuses.com/400\",\n  \"title\": \"Bad Request\",\n  \"status\": 400,\n  \"detail\": \"Template engine 'invalid' has not been registered\",\n  \"instance\": \"/api/pdf\"\n}\n```\n\n## 🛡️ Rate Limiting\n\nRate limiting is enforced per subscription:\n\n- **Window-based limiting**: Requests are counted within specified time windows\n- **Per-user limits**: Each API key has its own rate limit configuration\n- **429 Response**: When limits are exceeded, includes `Retry-After` header\n- **Configurable**: Administrators can set custom limits per subscription\n\nExample rate limit headers:\n```\nRetry-After: 60\n```\n\n## ⚙️ Configuration\n\n### Application Settings\n\nKey configuration options in `appsettings.json`:\n\n```json\n{\n  \"ConnectionStrings\": {\n    \"SqlConnection\": \"Server=.;Database=PdfSmith;Trusted_Connection=true;\"\n  },\n  \"AppSettings\": {\n    \"AdministratorUserName\": \"admin\",\n    \"AdministratorApiKey\": \"your-admin-api-key\"\n  },\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  }\n}\n```\n\n### Environment Variables\n\n- `PLAYWRIGHT_BROWSERS_PATH`: Custom path for Playwright browsers installation\n\n### Playwright Configuration\n\nChromium is automatically installed via Playwright. If `PLAYWRIGHT_BROWSERS_PATH` isn't specified, browsers are installed to the default locations:\n\n**Windows:**\n```\n%USERPROFILE%\\AppData\\Local\\ms-playwright\n```\n\n**Linux:**\n```\n~/.cache/ms-playwright\n```\n\n**macOS:**\n```\n~/Library/Caches/ms-playwright\n```\n\n## 🔧 Development\n\n### Project Structure\n\n```\nPdfSmith/\n├── src/\n│   ├── PdfSmith/                    # Main API project\n│   ├── PdfSmith.BusinessLayer/      # Business logic and services\n│   ├── PdfSmith.DataAccessLayer/    # Data access and entities\n│   └── PdfSmith.Shared/             # Shared models and contracts\n├── samples/\n│   └── PdfSmith.Client/             # Example client application\n└── README.md\n```\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests if applicable\n5. Submit a pull request\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## 🆘 Support\n\nFor issues, questions, or contributions, please visit the [GitHub repository](https://github.com/marcominerva/PdfSmith).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcominerva%2Fpdfsmith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcominerva%2Fpdfsmith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcominerva%2Fpdfsmith/lists"}