chore: cleanup
This commit is contained in:
+42
-11
@@ -1,13 +1,44 @@
|
|||||||
import process from "node:process";
|
import openapi from "@elysiajs/openapi";
|
||||||
import { AssetManager } from "./lib/assets";
|
import { Elysia, t } from "elysia";
|
||||||
|
import { version } from "../package.json";
|
||||||
|
import { pm } from "./lib/auth";
|
||||||
|
import { v1routes } from "./v1";
|
||||||
|
|
||||||
queueMicrotask(async () => {
|
const app = new Elysia()
|
||||||
const assets = new AssetManager();
|
.use(
|
||||||
assets.getMissingAssets();
|
openapi({
|
||||||
|
exclude: { paths: ["/", "/health"] },
|
||||||
|
documentation: { info: { title: "Astral API", version } },
|
||||||
|
path: "/docs",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.guard({
|
||||||
|
headers: t.Object({
|
||||||
|
authorization: t.Optional(
|
||||||
|
t.String({
|
||||||
|
pattern: "^Bearer [a-zA-Z0-9-_+=]+",
|
||||||
|
description: "API Key",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.derive(({ headers }) => {
|
||||||
|
const auth = headers.authorization;
|
||||||
|
const bearer = auth?.startsWith("Bearer ") ? auth.slice(7) : null;
|
||||||
|
|
||||||
setInterval(() => {
|
return {
|
||||||
assets.getMissingAssets();
|
bearer,
|
||||||
}, 86400000); // refresh assets every 24 hours
|
key: bearer ? pm.validate(bearer) : pm.invalid,
|
||||||
});
|
};
|
||||||
await import("./server");
|
})
|
||||||
console.log(`Worker ${process.pid} started`);
|
.get("/", ({ redirect }) => redirect("/docs"))
|
||||||
|
.get("/health", () => "OK")
|
||||||
|
.listen(3000);
|
||||||
|
|
||||||
|
v1routes(app);
|
||||||
|
|
||||||
|
export type App = typeof app;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
|
||||||
|
);
|
||||||
|
|||||||
+15
-3
@@ -1,3 +1,4 @@
|
|||||||
|
import { readdirSync } from "node:fs";
|
||||||
import { mkdir, readdir } from "node:fs/promises";
|
import { mkdir, readdir } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { file, hash, write } from "bun";
|
import { file, hash, write } from "bun";
|
||||||
@@ -11,6 +12,7 @@ export class AssetManager {
|
|||||||
private cache = new Map<string, ArrayBuffer>();
|
private cache = new Map<string, ArrayBuffer>();
|
||||||
private deduper = new Deduplicator<ArrayBuffer>();
|
private deduper = new Deduplicator<ArrayBuffer>();
|
||||||
private MAX = 300;
|
private MAX = 300;
|
||||||
|
private approximateSize = 0;
|
||||||
|
|
||||||
async getMissingAssets() {
|
async getMissingAssets() {
|
||||||
const avatars = await getEnkaAvatarStore();
|
const avatars = await getEnkaAvatarStore();
|
||||||
@@ -112,16 +114,26 @@ export class AssetManager {
|
|||||||
|
|
||||||
// a simple LRU
|
// a simple LRU
|
||||||
private touch(key: string, value: ArrayBuffer) {
|
private touch(key: string, value: ArrayBuffer) {
|
||||||
|
if (!this.cache.has(key)) this.approximateSize += value.byteLength;
|
||||||
// move to newest
|
// move to newest
|
||||||
this.cache.delete(key);
|
this.cache.delete(key);
|
||||||
this.cache.set(key, value);
|
this.cache.set(key, value);
|
||||||
|
|
||||||
// purge oldest
|
|
||||||
if (this.cache.size > this.MAX) {
|
if (this.cache.size > this.MAX) {
|
||||||
const oldest = this.cache.keys().next().value;
|
// purge oldest
|
||||||
this.cache.delete(oldest!);
|
const oldest = this.cache.keys().next().value!;
|
||||||
|
this.approximateSize -= this.cache.get(oldest)?.byteLength ?? 0;
|
||||||
|
this.cache.delete(oldest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get health() {
|
||||||
|
return {
|
||||||
|
lru: this.cache.size,
|
||||||
|
lruSize: `${(this.approximateSize / 1000).toFixed(2)}Kb`,
|
||||||
|
disk: readdirSync("store").length,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getEnkaStores() {
|
export async function getEnkaStores() {
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import openapi from "@elysiajs/openapi";
|
|
||||||
import { Elysia, t } from "elysia";
|
|
||||||
import { version } from "../package.json";
|
|
||||||
import { pm } from "./lib/auth";
|
|
||||||
import { v1routes } from "./v1";
|
|
||||||
|
|
||||||
const app = new Elysia()
|
|
||||||
.use(
|
|
||||||
openapi({
|
|
||||||
exclude: { paths: ["/", "/health"] },
|
|
||||||
documentation: { info: { title: "Astral API", version } },
|
|
||||||
path: "/docs",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.guard({
|
|
||||||
headers: t.Object({
|
|
||||||
authorization: t.Optional(
|
|
||||||
t.String({
|
|
||||||
pattern: "^Bearer [a-zA-Z0-9-_+=]+",
|
|
||||||
description: "API Key",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.derive(({ headers }) => {
|
|
||||||
const auth = headers.authorization;
|
|
||||||
const bearer = auth?.startsWith("Bearer ") ? auth.slice(7) : null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
bearer,
|
|
||||||
key: bearer ? pm.validate(bearer) : pm.invalid,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.get("/", ({ redirect }) => redirect("/docs"))
|
|
||||||
.get("/health", () => "OK")
|
|
||||||
.listen(3000);
|
|
||||||
|
|
||||||
v1routes(app);
|
|
||||||
|
|
||||||
export type App = typeof app;
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
|
|
||||||
);
|
|
||||||
+34
-10
@@ -11,7 +11,6 @@ import {
|
|||||||
TTLCache,
|
TTLCache,
|
||||||
} from "enka.ts";
|
} from "enka.ts";
|
||||||
import { twj } from "tw-to-css";
|
import { twj } from "tw-to-css";
|
||||||
import type { App } from "@";
|
|
||||||
import { generateBackground } from "@/assets/background";
|
import { generateBackground } from "@/assets/background";
|
||||||
import { ConstsSvg } from "@/assets/consts";
|
import { ConstsSvg } from "@/assets/consts";
|
||||||
import { featherImage } from "@/assets/feather";
|
import { featherImage } from "@/assets/feather";
|
||||||
@@ -22,6 +21,7 @@ import fontFile from "@/assets/shinshin.woff2" with { type: "file" };
|
|||||||
import { star } from "@/assets/stars";
|
import { star } from "@/assets/stars";
|
||||||
import { TalentSvg } from "@/assets/talent";
|
import { TalentSvg } from "@/assets/talent";
|
||||||
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
import { WeaponGlazeSvg } from "@/assets/weapon-glaze";
|
||||||
|
import type { App } from "@/index";
|
||||||
import { AssetManager, getEnkaStores } from "@/lib/assets";
|
import { AssetManager, getEnkaStores } from "@/lib/assets";
|
||||||
import { Deduplicator } from "@/lib/dedupe";
|
import { Deduplicator } from "@/lib/dedupe";
|
||||||
import { mockData } from "@/lib/mock";
|
import { mockData } from "@/lib/mock";
|
||||||
@@ -35,6 +35,15 @@ const deduper = new Deduplicator();
|
|||||||
const font = file(fontFile);
|
const font = file(fontFile);
|
||||||
const shade = file(shadeSvg);
|
const shade = file(shadeSvg);
|
||||||
|
|
||||||
|
queueMicrotask(async () => {
|
||||||
|
const assets = new AssetManager();
|
||||||
|
assets.getMissingAssets();
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
assets.getMissingAssets();
|
||||||
|
}, 86400000); // refresh assets every 24 hours
|
||||||
|
});
|
||||||
|
|
||||||
export function card(app: App) {
|
export function card(app: App) {
|
||||||
return app
|
return app
|
||||||
.group("/card", (app) =>
|
.group("/card", (app) =>
|
||||||
@@ -200,15 +209,30 @@ export function card(app: App) {
|
|||||||
|
|
||||||
const statRows = buildDisplayRows(char, locs);
|
const statRows = buildDisplayRows(char, locs);
|
||||||
|
|
||||||
if (debug)
|
if (debug) {
|
||||||
return JSON.stringify({
|
console.log(
|
||||||
char,
|
`${Date.now() - start}ms - Debug endpoint, returning JSON`,
|
||||||
weapon,
|
);
|
||||||
assets: resources.map((e) => e.src),
|
return JSON.stringify(
|
||||||
charLvl,
|
{
|
||||||
charLvlMax,
|
charLvl,
|
||||||
statRows,
|
charLvlMax,
|
||||||
});
|
cacheKey,
|
||||||
|
cache: assets.health,
|
||||||
|
assets: Object.fromEntries(
|
||||||
|
resources.map((e) => [
|
||||||
|
e.src,
|
||||||
|
`${(e.data.byteLength / 1000).toFixed(2)}Kb`,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
char,
|
||||||
|
weapon,
|
||||||
|
statRows,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const res = new ImageResponse(
|
const res = new ImageResponse(
|
||||||
<div tw="relative text-[32.25px] text-white rounded-xl overflow-hidden">
|
<div tw="relative text-[32.25px] text-white rounded-xl overflow-hidden">
|
||||||
|
|||||||
+1
-1
@@ -103,7 +103,7 @@
|
|||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"],
|
"@/*": ["./src/*"],
|
||||||
"@": ["./src/server.ts"]
|
"@": ["./src/index.ts"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user