Skip to content

Commit 11fb5c7

Browse files
committed
Add example endpoint to create certificates
1 parent 0890ce0 commit 11fb5c7

File tree

10 files changed

+220
-95
lines changed

10 files changed

+220
-95
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Oicana.Example.Models;
3+
using Oicana.Example.Services;
4+
5+
namespace Oicana.Example.Controllers;
6+
7+
/// <summary>
8+
/// Create certificates
9+
/// </summary>
10+
/// <param name="certificateService"></param>
11+
[ApiController]
12+
[Route("certificates")]
13+
public class CertificateController(ICertificateService certificateService) : ControllerBase
14+
{
15+
/// <summary>
16+
/// Create a Certificate
17+
/// </summary>
18+
/// <param name="request"></param>
19+
/// <returns>The compiled PDF certificate</returns>
20+
[HttpPost]
21+
public async Task<IActionResult> CompilePdfTemplate([FromBody] CreateCertificate request)
22+
{
23+
var certificate = await certificateService.CreateCertificate(request);
24+
if (certificate == null)
25+
{
26+
return StatusCode(500);
27+
}
28+
29+
var file = new FileStreamResult(certificate, "application/pdf")
30+
{
31+
FileDownloadName = $"certificate_{DateTimeOffset.Now:yyyy_MM_dd_HH_mm_ss_ffff}.pdf"
32+
};
33+
34+
return file;
35+
}
36+
}

Controllers/PdfTemplatingController.cs

Lines changed: 5 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
using System.Diagnostics;
22
using Microsoft.AspNetCore.Mvc;
33
using Oicana.Example.Services;
4-
using Oicana.Inputs;
54
using System.Text.RegularExpressions;
5+
using Oicana.Example.Models;
66

77
namespace Oicana.Example.Controllers;
88

@@ -16,7 +16,7 @@ namespace Oicana.Example.Controllers;
1616
public class PdfTemplatingController(ILogger<PdfTemplatingController> logger, ITemplatingService templatingService) : ControllerBase
1717
{
1818
/// <summary>
19-
/// Compile a template with given input
19+
/// Compile any example template to PDF with given inputs
2020
/// </summary>
2121
/// <param name="template" example="table"></param>
2222
/// <param name="request"></param>
@@ -49,8 +49,9 @@ public async Task<IActionResult> CompilePdfTemplate([FromRoute] String template,
4949
return StatusCode(400);
5050
}
5151
}
52+
5253
/// <summary>
53-
/// Compile a template with given input
54+
/// Compile any example template to PNG with given inputs
5455
/// </summary>
5556
/// <param name="template" example="table"></param>
5657
/// <param name="request"></param>
@@ -128,94 +129,4 @@ public IActionResult DownloadTemplate([FromRoute] String template)
128129
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
129130
return File(fileStream, "application/zip", $"{template}.zip");
130131
}
131-
}
132-
133-
/// <summary>
134-
/// Request to compile a template with given input
135-
/// </summary>
136-
/// <example>
137-
/// {
138-
/// "input": [
139-
/// {
140-
/// "key": "input",
141-
/// "value": {
142-
/// "description": "from sample data",
143-
/// "rows": [
144-
/// {
145-
/// "name": "Frank",
146-
/// "one": "first",
147-
/// "two": "second",
148-
/// "three": "third"
149-
/// },
150-
/// {
151-
/// "name": "John",
152-
/// "one": "first_john",
153-
/// "two": "second_john",
154-
/// "three": "third_john"
155-
/// }
156-
/// ]
157-
/// }
158-
/// }
159-
/// ]
160-
/// }
161-
/// </example>
162-
public class CompilePdfRequest
163-
{
164-
/// <summary>
165-
/// Input json to compile the template with
166-
/// </summary>
167-
/// <example>
168-
/// [
169-
/// {
170-
/// "key": "data",
171-
/// "value": {
172-
/// "description": "from sample data",
173-
/// "rows": [
174-
/// {
175-
/// "name": "Frank",
176-
/// "one": "first",
177-
/// "two": "second",
178-
/// "three": "third"
179-
/// },
180-
/// {
181-
/// "name": "John",
182-
/// "one": "first_john",
183-
/// "two": "second_john",
184-
/// "three": "third_john"
185-
/// }
186-
/// ]
187-
/// }
188-
/// }
189-
/// ]
190-
/// </example>
191-
public required IList<TemplateJsonInput> JsonInputs { get; init; }
192-
193-
/// <summary>
194-
///
195-
/// </summary>
196-
/// <example>
197-
/// [
198-
/// {
199-
/// "key": "logo",
200-
/// "blobId": "00000000-0000-0000-0000-000000000000"
201-
/// }
202-
/// ]
203-
/// </example>
204-
public required IList<StoredBlobInput> BlobInputs { get; init; }
205-
}
206-
207-
/// <summary>
208-
/// Pass a blob stored in the service as a blob input to the template
209-
/// </summary>
210-
public class StoredBlobInput
211-
{
212-
/// <summary>
213-
/// The key of the blob input
214-
/// </summary>
215-
public required string Key { get; init; }
216-
217-
/// <summary>
218-
/// Identifier of the blob file
219-
/// </summary>
220-
public required Guid BlobId { get; init; }
221-
}
132+
}

Models/CompilePdfRequest.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Oicana.Inputs;
2+
3+
namespace Oicana.Example.Models;
4+
5+
/// <summary>
6+
/// Request to compile a template with given input
7+
/// </summary>
8+
/// <example>
9+
/// {
10+
/// "jsonInputs": [
11+
/// {
12+
/// "key": "input",
13+
/// "value": {
14+
/// "description": "from sample data",
15+
/// "rows": [
16+
/// {
17+
/// "name": "Frank",
18+
/// "one": "first",
19+
/// "two": "second",
20+
/// "three": "third"
21+
/// },
22+
/// {
23+
/// "name": "John",
24+
/// "one": "first_john",
25+
/// "two": "second_john",
26+
/// "three": "third_john"
27+
/// }
28+
/// ]
29+
/// }
30+
/// }
31+
/// ]
32+
/// }
33+
/// </example>
34+
public class CompilePdfRequest
35+
{
36+
/// <summary>
37+
/// Input json to compile the template with
38+
/// </summary>
39+
/// <example>
40+
/// [
41+
/// {
42+
/// "key": "data",
43+
/// "value": {
44+
/// "description": "from sample data",
45+
/// "rows": [
46+
/// {
47+
/// "name": "Frank",
48+
/// "one": "first",
49+
/// "two": "second",
50+
/// "three": "third"
51+
/// },
52+
/// {
53+
/// "name": "John",
54+
/// "one": "first_john",
55+
/// "two": "second_john",
56+
/// "three": "third_john"
57+
/// }
58+
/// ]
59+
/// }
60+
/// }
61+
/// ]
62+
/// </example>
63+
public required IList<TemplateJsonInput> JsonInputs { get; init; }
64+
65+
/// <summary>
66+
///
67+
/// </summary>
68+
/// <example>
69+
/// [
70+
/// {
71+
/// "key": "logo",
72+
/// "blobId": "00000000-0000-0000-0000-000000000000"
73+
/// }
74+
/// ]
75+
/// </example>
76+
public required IList<StoredBlobInput> BlobInputs { get; init; }
77+
}

Models/CreateCertificate.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace Oicana.Example.Models;
2+
3+
/// <summary>
4+
/// Payload to create a certificate
5+
/// </summary>
6+
/// <example>
7+
/// {
8+
/// "name": "Jane Doe"
9+
/// }
10+
/// </example>
11+
public class CreateCertificate
12+
{
13+
/// <summary>
14+
/// Name to create the certificate for
15+
/// </summary>
16+
/// <example>Jane Doe</example>
17+
public required string Name { get; set; }
18+
}

Models/StoredBlobInput.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Oicana.Example.Models;
2+
3+
/// <summary>
4+
/// Pass a blob stored in the service as a blob input to the template
5+
/// </summary>
6+
public class StoredBlobInput
7+
{
8+
/// <summary>
9+
/// The key of the blob input
10+
/// </summary>
11+
public required string Key { get; init; }
12+
13+
/// <summary>
14+
/// Identifier of the blob file
15+
/// </summary>
16+
public required Guid BlobId { get; init; }
17+
}

Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
});
1616
builder.Services.AddSingleton<IOicanaService, OicanaService>();
1717
builder.Services.AddScoped<ITemplatingService, TemplatingService>()
18+
.AddScoped<ICertificateService, CertificateService>()
1819
.AddScoped<IStoredBlobService, StoredBlobService>();
1920
builder.Services.AddCors(options =>
2021
{

Services/CertificateService.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System.Diagnostics;
2+
using System.Text.Json;
3+
using Oicana.Config;
4+
using Oicana.Example.Models;
5+
using Oicana.Inputs;
6+
using Oicana.Template;
7+
8+
namespace Oicana.Example.Services;
9+
10+
/// <inheritdoc />
11+
public class CertificateService(IOicanaService oicanaService, ILogger<CertificateService> logger) : ICertificateService
12+
{
13+
private static readonly string Template = "certificate";
14+
15+
/// <inheritdoc />
16+
public async Task<Stream?> CreateCertificate(CreateCertificate request)
17+
{
18+
var template = oicanaService.GetTemplate(Template);
19+
if (template == null)
20+
{
21+
return null;
22+
}
23+
24+
// You could load any additional data here that your template needs
25+
// For example, you could have a database with grades to look up and pass into your template
26+
try
27+
{
28+
var watch = new Stopwatch();
29+
watch.Start();
30+
31+
// "certificate" is the key defined in the template
32+
// See https://github.com/oicana/oicana-example-templates/blob/672967c5b667dfa845228cac443d32b8b3c7ae0a/templates/certificate/typst.toml#L12
33+
// We can use `TemplateJsonInput.From` here, because `CreateCertificate` serializes into a valid value for the certificate input
34+
// In a production system you most likely want separate types as API Model and for the input representation
35+
var input = TemplateJsonInput.From("certificate", request, new JsonSerializerOptions(JsonSerializerDefaults.Web));
36+
var result = await Task.Run<Stream?>(() => template.Compile([input], [], CompilationOptions.Pdf()));
37+
watch.Stop();
38+
39+
logger.LogInformation("Certificate generated in {ElapsedMilliseconds}ms", watch.ElapsedMilliseconds);
40+
return result;
41+
}
42+
catch (Exception e)
43+
{
44+
logger.LogError("Error while compiling a certificate: {e}", e);
45+
return null;
46+
}
47+
}
48+
}

Services/ICertificateService.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Oicana.Example.Models;
2+
3+
namespace Oicana.Example.Services;
4+
5+
/// <summary>
6+
/// Service for creating certificates
7+
/// </summary>
8+
public interface ICertificateService
9+
{
10+
/// <summary>
11+
/// Create a certificate for the given data
12+
/// </summary>
13+
/// <param name="request"></param>
14+
/// <returns>Compiled certificate PDf file</returns>
15+
Task<Stream?> CreateCertificate(CreateCertificate request);
16+
}

Services/ITemplatingService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Oicana.Example.Controllers;
2+
using Oicana.Example.Models;
23
using Oicana.Inputs;
34

45
namespace Oicana.Example.Services;

Services/TemplatingService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using Oicana.Config;
2-
using Oicana.Example.Controllers;
2+
using Oicana.Example.Models;
33
using Oicana.Inputs;
44
using Oicana.Template;
55

0 commit comments

Comments
 (0)