Kyoo/api/src/base.ts
2026-03-11 12:40:33 +01:00

146 lines
3.7 KiB
TypeScript

import { getLogger } from "@logtape/logtape";
import { Elysia, t } from "elysia";
import { auth } from "./auth";
import { entriesH } from "./controllers/entries";
import { imagesH } from "./controllers/images";
import { historyH } from "./controllers/profiles/history";
import { nextup } from "./controllers/profiles/nextup";
import { watchlistH } from "./controllers/profiles/watchlist";
import { seasonsH } from "./controllers/seasons";
import { seed } from "./controllers/seed";
import { videoLinkH } from "./controllers/seed/video-links";
import { videosWriteH } from "./controllers/seed/videos";
import { collections } from "./controllers/shows/collections";
import { movies } from "./controllers/shows/movies";
import { series } from "./controllers/shows/series";
import { showsH } from "./controllers/shows/shows";
import { staffH } from "./controllers/staff";
import { studiosH } from "./controllers/studios";
import { videosMetadata } from "./controllers/video-metadata";
import { videosReadH } from "./controllers/videos";
import { dbRaw } from "./db";
import { KError } from "./models/error";
import { appWs } from "./websockets";
const logger = getLogger();
export const base = new Elysia({ name: "base" })
.onError(({ code, error }) => {
// sometimes elysia as an unknown code when throwing errors
if (code === "UNKNOWN") {
try {
const details = JSON.parse(error.message);
if (details?.code === "KError") {
const { code, ...ret } = details;
return ret;
}
} catch {}
}
if (code === "VALIDATION") {
return {
status: error.status,
message: `Validation error.`,
details: error,
} as KError;
}
if (code === "NOT_FOUND") {
return error;
}
logger.error("Request error code={code} error={error}", {
code: code,
error: error,
});
return {
status: 500,
message: "Internal server error",
} as KError;
})
.get("/health", () => ({ status: "healthy" }) as const, {
detail: { description: "Check if the api is healthy." },
response: {
200: t.Union([
t.Object({
status: t.Literal("a"),
a: t.Object({ b: t.Integer() }),
}),
t.Object({ status: t.Literal("healthy") }),
]),
},
})
.get(
"/ready",
async ({ status }) => {
try {
await dbRaw.execute("select 1");
return { status: "healthy", database: "healthy" } as const;
} catch (e) {
return status(500, {
status: "unhealthy",
database: e,
});
}
},
{
detail: { description: "Check if the api is healthy." },
response: {
200: t.Object({
status: t.Literal("healthy"),
database: t.Literal("healthy"),
}),
500: t.Object({
status: t.Literal("unhealthy"),
database: t.Any(),
}),
},
},
)
.as("global");
export const prefix = "/api";
export const handlers = new Elysia({ prefix })
.use(base)
.use(appWs)
.use(auth)
.guard(
{
// Those are not applied for now. See https://github.com/elysiajs/elysia/issues/1139
detail: {
security: [{ bearer: ["core.read"] }, { api: ["core.read"] }],
},
response: {
401: { ...KError, description: "" },
403: { ...KError, description: "" },
},
permissions: ["core.read"],
},
(app) =>
app
.use(showsH)
.use(movies)
.use(series)
.use(collections)
.use(entriesH)
.use(seasonsH)
.use(studiosH)
.use(staffH)
.use(imagesH)
.use(watchlistH)
.use(historyH)
.use(nextup)
.use(videosReadH)
.use(videosMetadata),
)
.guard(
{
detail: {
security: [{ bearer: ["core.write"] }, { api: ["core.write"] }],
},
response: {
401: { ...KError, description: "" },
403: { ...KError, description: "" },
},
permissions: ["core.write"],
},
(app) => app.use(videosWriteH).use(videoLinkH).use(seed),
);