Skip to content

Commit

Permalink
feat(badges): update backend code to use makeLogo from badges/shields…
Browse files Browse the repository at this point in the history
… repo

Signed-off-by: Andrei Jiroh Halili (RecapTime.dev) <[email protected]>
  • Loading branch information
ajhalili2006 committed Aug 30, 2024
1 parent 6ec5cca commit 03a4eb4
Show file tree
Hide file tree
Showing 3 changed files with 311 additions and 94 deletions.
173 changes: 108 additions & 65 deletions api/badges.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { Bool, OpenAPIRoute, Str } from "chanfana";
import { Context } from "hono";
import { z } from "zod";
import { getBadgeData, resolveBadgeIcon } from "../lib/db.ts";
import { Format } from "../utils/types.ts";
import { makeBadge } from "badge-maker";
import { BadgeData, getBadgeData, resolveBadgeIcon } from "../lib/db.ts";
import { Format, makeBadge } from "badge-maker";
import { makeLogo } from "../lib/logos.ts";

export class hcbBalanceOps extends OpenAPIRoute {
schema = {
tags: ["badges"],
summary: "Generate a SVG badge of a HCB organization's balances",
descritpion: `\
By default without the \`org\` query parameter, it will uses data from [Hack Club HQ](https://hcb.hackclub.com/api/v3/organizations/hq),
but it will change to either \`recaptime-dev\` or \`lorebooks-wiki\` in the future.
`,
};
}

export class generateSvg extends OpenAPIRoute {
schema = {
Expand Down Expand Up @@ -58,77 +69,109 @@ including \`logo\` (not \`logoBase64\` for abuse prevention) and \`style\`.

async handle(c: Context) {
const apiReqData = await this.getValidatedData<typeof this.schema>();
const acceptCT = c.req.header("Accept");
const dbData = await getBadgeData(
apiReqData.params.project,
apiReqData.params.badgeName
);
console.log(dbData);

if (
apiReqData.query.json == true ||
acceptCT?.includes("application/json")
) {
if (dbData.result == null && dbData.versionStamp == null) {
return c.json(
{
ok: false,
result: null,
versionStamp: null,
error: "project and badge name combination not found",
},
404
);
}
return c.json(dbData);
}
try {
const acceptCT = c.req.header("Accept");
const dbData = await getBadgeData(
apiReqData.params.project,
apiReqData.params.badgeName
);
console.log(dbData);
const {
type,
data,
}: {
type: "redirect" | "badge" | undefined;
data: BadgeData | undefined;
} = dbData.result;

if (dbData.result == null && dbData.versionStamp == null) {
const Badge404 = makeBadge({
label: "404",
message: "not found",
color: "red",
});
return c.newResponse(Badge404, 404, {
"Content-Type": "image/svg+xml",
});
}
type overridesData = {
logoColor?: string;
logoSize?: string;
logo?: string;
};
const overrides: overridesData = {
logoColor: apiReqData.params.logoColor || "white",
logoSize: apiReqData.params.logoSize,
logo: apiReqData.params.logo || data.logo,
};

const { type, data } = dbData.result;
if (
apiReqData.query.json == true ||
acceptCT?.includes("application/json")
) {
if (dbData.result == null && dbData.versionStamp == null) {
return c.json(
{
ok: false,
result: null,
versionStamp: null,
error: "project and badge name combination not found",
},
404
);
}
return c.json(dbData);
}

if (type == "redirect") {
return c.redirect(data.redirectUrl);
} else if (type == "badge") {
const logoBase64 =
data.logo != null ? await resolveBadgeIcon(data.logo) : null;
const badgeData: Format = {
style: apiReqData.query.style || data.style,
message: data.message,
color: data.color,
};
if (logoBase64 != null) Object.assign(badgeData, { logoBase64 });
if (data.label != null)
Object.assign(badgeData, {
label: data.label,
labelColor: data.labelColor,
if (dbData.result == null && dbData.versionStamp == null) {
const Badge404 = makeBadge({
label: "404",
message: "not found",
color: "red",
});

try {
const resultSvg = makeBadge(badgeData);
return c.newResponse(resultSvg, 200, {
return c.newResponse(Badge404, 404, {
"Content-Type": "image/svg+xml",
});
} catch (_error) {
Error(_error);
const resultSvgError = makeBadge({
label: "error",
message: "something went wrong",
color: "red",
});
return c.newResponse(resultSvgError, 500, {
}

if (type == "redirect") {
return c.redirect(
data?.redirectUrl !== undefined
? data?.redirectUrl
: "https://badges.api.lorebooks.wiki/badges/notfound/notfound"
);
} else if (type == "badge") {
console.log(`logo name: ${data.logo}`);
let logoData: string | null;

if (data?.logo?.startsWith("data:")) {
logoData = await resolveBadgeIcon(data.logo);
} else {
logoData = makeLogo(data.logo, overrides);
}
console.log(`logo data - ${logoData}`);
let badgeData: Format = {
message: data.message,
color: data?.color || "gray",
};
if (typeof data?.label == "string") {
Object.assign(badgeData, {
label: data?.label,
labelColor: data?.labelColor,
});
}
if (logoData != null) {
Object.assign(badgeData, {
logoBase64: logoData,
});
}

let badge = makeBadge(badgeData);
return c.newResponse(badge, 200, {
"Content-Type": "image/svg+xml",
});
}
} catch (error) {
console.error(error);
const resultSvgError = makeBadge({
label: "error",
message: "something went wrong",
color: "red",
style: apiReqData.query.style || "flat",
});
return c.newResponse(resultSvgError, 500, {
"Content-Type": "image/svg+xml",
});
}
}
}
67 changes: 38 additions & 29 deletions lib/db.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { config } from "./config.ts";

export type RedirectTool = {
redirectUrl?: string;
};

export const kv = async (kvUrl?: string) => {
if (kvUrl !== undefined) {
return await Deno.openKv(kvUrl)
return await Deno.openKv(kvUrl);
} else {
return await Deno.openKv()
return await Deno.openKv();
}
}
};

export type BadgeData = {
redirectUrl: string;
label?: string | null;
message: string;
labelColor?: string | null;
Expand All @@ -23,65 +20,77 @@ export type BadgeData = {
};

export type BadgeMakerParams = BadgeData & {
logoBase64?: string | null
logoBase64?: string | null;
};
export type DbResult = {
ok: boolean;
result: {
type: "redirect" | "badge";
data: RedirectTool & BadgeData;
data: BadgeData;
} | null;
versionStamp: string | null,
error?: string | object
versionStamp: string | null;
error?: string | object;
};

export async function getBadgeData(project: string, badgeName: string): Promise<DbResult> {
const kvApi = await kv(config.kvUrl)
export async function getBadgeData(
project: string,
badgeName: string
): Promise<DbResult> {
const kvApi = await kv(config.kvUrl);
try {
const {value,versionstamp} = await kvApi.get([`staticBadges`, project, badgeName])
const { value, versionstamp } = await kvApi.get([
`staticBadges`,
project,
badgeName,
]);

return {
ok: true,
result: value,
versionStamp: versionstamp
versionStamp: versionstamp,
};
} catch (error) {
return {
ok: false,
result: null,
versionStamp: null,
error
}
error,
};
}
}

export async function setBadgeData(project: string, badgeName: string, type: "redirect" | "badge", data: RedirectTool | BadgeData) {
const kvApi = await kv(config.kvUrl)
export async function setBadgeData(
project: string,
badgeName: string,
type: "redirect" | "badge",
data: BadgeData
) {
const kvApi = await kv(config.kvUrl);
try {
const key = [`staticBadges`, project, badgeName]
const kvResult = await kvApi.set(key, {type, data});
const key = [`staticBadges`, project, badgeName];
const kvResult = await kvApi.set(key, { type, data });
return {
ok: kvResult.ok,
versionStamp: kvResult.versionstamp
}
versionStamp: kvResult.versionstamp,
};
} catch (error) {
return {
ok: false,
versionStamp: null,
error
}
error,
};
}
}

export async function resolveBadgeIcon(icon: string) {
const kvApi = await kv(config.kvUrl);
try {
const result = await kvApi.get<string|null>(["badgeIcons", icon])
const result = await kvApi.get<string | null>(["badgeIcons", icon]);
if (result.value == null && result.versionstamp == null) {
return null
return null;
}
return result.value
return result.value;
} catch (error) {
throw Error(error)
throw Error(error);
}
}
Loading

0 comments on commit 03a4eb4

Please sign in to comment.