feat(api): dynamic generation using query params
This commit is contained in:
@@ -17,7 +17,8 @@
|
|||||||
"nuxt": "^3.13.0",
|
"nuxt": "^3.13.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest"
|
"vue-router": "latest",
|
||||||
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.11.0",
|
"packageManager": "pnpm@9.11.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
7522
pnpm-lock.yaml
generated
7522
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,31 @@
|
|||||||
import { createCanvas, loadImage } from "canvas";
|
import { createCanvas, loadImage } from "canvas";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { resolve } from "path";
|
||||||
|
|
||||||
const time = (label: string) => {
|
const time = (label: string) => {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
return () => {
|
return () => {
|
||||||
console.log(label, "\t", performance.now() - start);
|
console.log(label, "\t", performance.now() - start);
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const querySchema = z.object({
|
||||||
|
logo: z.string().min(1),
|
||||||
|
content: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// TODO: define logo based on name
|
const parsed = querySchema.safeParse(getQuery(event));
|
||||||
const name = getRouterParam(event, "name");
|
if (!parsed.success) {
|
||||||
name;
|
return createError({
|
||||||
|
status: 400,
|
||||||
|
message: `Invalid request query: missing ${parsed.error.errors.map((x) => x.path.join(".")).join(", ")}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const text = "https://pihkaal.me"
|
const logo = parsed.data.logo;
|
||||||
|
const text = parsed.data.content;
|
||||||
event.node.res.setHeader("Content-Type", "text/plain");
|
|
||||||
|
|
||||||
const tt = time("total");
|
const tt = time("total");
|
||||||
let t;
|
let t;
|
||||||
@@ -25,16 +35,20 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
t = time("render");
|
t = time("render");
|
||||||
const canvas = createCanvas(SIZE, SIZE);
|
const canvas = createCanvas(SIZE, SIZE);
|
||||||
await QRCode.toCanvas(canvas, text, { errorCorrectionLevel: 'H', width: SIZE, margin: 1 });
|
await QRCode.toCanvas(canvas, text, {
|
||||||
|
errorCorrectionLevel: "H",
|
||||||
|
width: SIZE,
|
||||||
|
margin: 1,
|
||||||
|
});
|
||||||
t();
|
t();
|
||||||
|
|
||||||
t = time("count");
|
t = time("count");
|
||||||
const qrCode = QRCode.create(text, { errorCorrectionLevel: 'H' });
|
const qrCode = QRCode.create(text, { errorCorrectionLevel: "H" });
|
||||||
const moduleCount = qrCode.modules.size + 2;
|
const moduleCount = qrCode.modules.size + 2;
|
||||||
t();
|
t();
|
||||||
|
|
||||||
t = time("logo");
|
t = time("logo");
|
||||||
const logo = await loadImage("http://localhost:3000/session.png");
|
const logoImage = await loadImage(resolve("public", `${logo}.png`));
|
||||||
|
|
||||||
const moduleSize = SIZE / moduleCount;
|
const moduleSize = SIZE / moduleCount;
|
||||||
|
|
||||||
@@ -44,15 +58,20 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const backgroundSize = logoModules * moduleSize + 1;
|
const backgroundSize = logoModules * moduleSize + 1;
|
||||||
const backgroundPosition = moduleSize * (moduleCount - logoModules) / 2;
|
const backgroundPosition = (moduleSize * (moduleCount - logoModules)) / 2;
|
||||||
|
|
||||||
const logoSize = backgroundSize - LOGO_PADDING * 2;
|
const logoSize = backgroundSize - LOGO_PADDING * 2;
|
||||||
const logoPosition = backgroundPosition + LOGO_PADDING;
|
const logoPosition = backgroundPosition + LOGO_PADDING;
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
ctx.fillStyle = "white";
|
ctx.fillStyle = "white";
|
||||||
ctx.fillRect(backgroundPosition, backgroundPosition, backgroundSize, backgroundSize);
|
ctx.fillRect(
|
||||||
ctx.drawImage(logo, logoPosition, logoPosition, logoSize, logoSize);
|
backgroundPosition,
|
||||||
|
backgroundPosition,
|
||||||
|
backgroundSize,
|
||||||
|
backgroundSize,
|
||||||
|
);
|
||||||
|
ctx.drawImage(logoImage, logoPosition, logoPosition, logoSize, logoSize);
|
||||||
t();
|
t();
|
||||||
|
|
||||||
t = time("buffer");
|
t = time("buffer");
|
||||||
@@ -61,5 +80,6 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
tt();
|
tt();
|
||||||
|
|
||||||
|
event.node.res.setHeader("Content-Type", "image/png");
|
||||||
return image;
|
return image;
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user