Skip to content

Commit 80fa3a2

Browse files
committed
Imrpoved tempalte retrieval, and improved xml embedding
1 parent 4aa5d0e commit 80fa3a2

File tree

4 files changed

+60
-49
lines changed

4 files changed

+60
-49
lines changed

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.8.0
1+
1.8.1

backend/src/controllers/templates.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,21 @@ export const getTemplates = () => {
228228

229229
export const getTemplateById = (id: string) => {
230230
const db = getDatabase();
231-
return db
232-
.prepare("SELECT * FROM templates WHERE id = ?")
233-
.get(id) as Template | undefined;
231+
const rows = db.query(
232+
"SELECT id, name, html, is_default, created_at FROM templates WHERE id = ? LIMIT 1",
233+
[id],
234+
);
235+
if (rows.length === 0) {
236+
return undefined;
237+
}
238+
const row = rows[0] as unknown[];
239+
return {
240+
id: row[0] as string,
241+
name: row[1] as string,
242+
html: row[2] as string,
243+
isDefault: Boolean(row[3]),
244+
createdAt: new Date(row[4] as string),
245+
} as Template;
234246
};
235247

236248
let builtInDefaultTemplate: Template | null | undefined;

backend/src/utils/jwt.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { create, decode, verify } from "djwt";
2-
3-
const secretKey = Deno.env.get("JWT_SECRET")
2+
import { getJwtSecret } from "./env.ts";
43

54
async function getKey(): Promise<CryptoKey> {
5+
const secretKey = getJwtSecret();
6+
const secretBytes = new TextEncoder().encode(secretKey);
7+
if (secretBytes.length === 0) {
8+
throw new Error("JWT_SECRET must not be empty");
9+
}
610
const key = await crypto.subtle.importKey(
711
"raw",
8-
new TextEncoder().encode(secretKey),
12+
secretBytes,
913
{ name: "HMAC", hash: "SHA-256" },
1014
false,
1115
["sign", "verify"],

backend/src/utils/pdf.ts

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
PDFArray,
33
PDFDict,
44
PDFDocument,
5-
PDFHexString,
65
PDFName,
76
PDFNumber,
87
PDFString,
@@ -42,7 +41,7 @@ function escapeHtml(value: unknown): string {
4241
.replace(/'/g, "&#39;");
4342
}
4443

45-
function escapeHtmlWithBreaks(value: unknown): string {
44+
function _escapeHtmlWithBreaks(value: unknown): string {
4645
return escapeHtml(value).replace(/\r?\n/g, "<br />");
4746
}
4847

@@ -342,8 +341,9 @@ export async function generateInvoicePDF(
342341
`${profile.name} export embedded by Invio`,
343342
opts?.locale || invoiceData.locale || inlined?.locale || "en-US",
344343
);
345-
} catch (_e) {
346-
// Silently ignore embedding failures to avoid breaking download
344+
} catch (error) {
345+
console.warn("Failed to embed XML attachment:", error);
346+
// Continue without attachment to avoid breaking download
347347
}
348348
}
349349
return pdfBytes as Uint8Array;
@@ -514,13 +514,6 @@ async function convertPdfToPdfA3(pdfBytes: Uint8Array): Promise<Uint8Array | nul
514514
}
515515
}
516516

517-
function toHexUpper(bytes: Uint8Array): string {
518-
return Array.from(bytes)
519-
.map((b) => b.toString(16).padStart(2, "0"))
520-
.join("")
521-
.toUpperCase();
522-
}
523-
524517
async function embedXmlAttachment(
525518
pdfBytes: Uint8Array,
526519
xmlBytes: Uint8Array,
@@ -532,23 +525,19 @@ async function embedXmlAttachment(
532525
const pdfDoc = await PDFDocument.load(pdfBytes, { updateMetadata: false });
533526
const context = pdfDoc.context;
534527
const now = new Date();
535-
const xmlBuffer = xmlBytes.buffer.slice(
536-
xmlBytes.byteOffset,
537-
xmlBytes.byteOffset + xmlBytes.byteLength,
538-
);
539-
const checksumBuffer = await crypto.subtle.digest("MD5", xmlBuffer as ArrayBuffer);
540-
const checksum = toHexUpper(new Uint8Array(checksumBuffer));
541-
542528
const paramsDict = context.obj({
543529
Size: PDFNumber.of(xmlBytes.length),
544-
CheckSum: PDFHexString.of(checksum),
545530
CreationDate: PDFString.fromDate(now),
546531
ModDate: PDFString.fromDate(now),
547532
});
548533

534+
const subtypeName = mediaType.includes("/")
535+
? mediaType.replace("/", "#2F")
536+
: mediaType;
537+
549538
const embeddedFileStream = context.stream(xmlBytes, {
550539
Type: PDFName.of("EmbeddedFile"),
551-
Subtype: PDFName.of(mediaType.replace("/", "#2F")),
540+
Subtype: PDFName.of(subtypeName),
552541
Params: paramsDict,
553542
});
554543
const embeddedFileRef = context.register(embeddedFileStream);
@@ -568,34 +557,40 @@ async function embedXmlAttachment(
568557
});
569558
const fileSpecRef = context.register(fileSpecDict);
570559

571-
let namesDict = pdfDoc.catalog.lookup(PDFName.of("Names"), PDFDict);
572-
if (!namesDict) {
573-
namesDict = context.obj({});
574-
pdfDoc.catalog.set(PDFName.of("Names"), namesDict);
560+
let namesDict = pdfDoc.catalog.get(PDFName.of("Names"));
561+
if (!(namesDict instanceof PDFDict)) {
562+
const created = context.obj({});
563+
pdfDoc.catalog.set(PDFName.of("Names"), created);
564+
namesDict = created;
575565
}
566+
const namesDictObj = namesDict as PDFDict;
576567

577-
let embeddedFilesDict = namesDict.lookup(PDFName.of("EmbeddedFiles"), PDFDict);
578-
if (!embeddedFilesDict) {
579-
embeddedFilesDict = context.obj({
580-
Names: context.obj([]),
581-
});
582-
namesDict.set(PDFName.of("EmbeddedFiles"), embeddedFilesDict);
568+
let embeddedFilesDict = namesDictObj.get(PDFName.of("EmbeddedFiles"));
569+
if (!(embeddedFilesDict instanceof PDFDict)) {
570+
const created = context.obj({});
571+
namesDictObj.set(PDFName.of("EmbeddedFiles"), created);
572+
embeddedFilesDict = created;
583573
}
574+
const embeddedFilesDictObj = embeddedFilesDict as PDFDict;
584575

585-
let namesArray = embeddedFilesDict.lookup(PDFName.of("Names"), PDFArray);
586-
if (!namesArray) {
587-
namesArray = context.obj([]);
588-
embeddedFilesDict.set(PDFName.of("Names"), namesArray);
576+
let namesArray = embeddedFilesDictObj.get(PDFName.of("Names"));
577+
if (!(namesArray instanceof PDFArray)) {
578+
const created = context.obj([]);
579+
embeddedFilesDictObj.set(PDFName.of("Names"), created);
580+
namesArray = created;
589581
}
590-
namesArray.push(PDFString.of(fileName));
591-
namesArray.push(fileSpecRef);
592-
593-
let afArray = pdfDoc.catalog.lookup(PDFName.of("AF"), PDFArray);
594-
if (!afArray) {
595-
afArray = context.obj([]);
596-
pdfDoc.catalog.set(PDFName.of("AF"), afArray);
582+
const namesArrayObj = namesArray as PDFArray;
583+
namesArrayObj.push(PDFString.of(fileName));
584+
namesArrayObj.push(fileSpecRef);
585+
586+
let afArray = pdfDoc.catalog.get(PDFName.of("AF"));
587+
if (!(afArray instanceof PDFArray)) {
588+
const created = context.obj([]);
589+
pdfDoc.catalog.set(PDFName.of("AF"), created);
590+
afArray = created;
597591
}
598-
afArray.push(fileSpecRef);
592+
const afArrayObj = afArray as PDFArray;
593+
afArrayObj.push(fileSpecRef);
599594

600595
pdfDoc.setSubject(`Embedded XML: ${fileName}`);
601596
pdfDoc.setKeywords(["Invoice", "Embedded XML", fileName]);

0 commit comments

Comments
 (0)