227 lines
9.6 KiB
TypeScript
227 lines
9.6 KiB
TypeScript
import { Elysia, t } from "elysia";
|
|
import { join } from "node:path";
|
|
import type { Document, QueryListOption } from "dbtype";
|
|
import type { DocumentAccessor } from "../model/doc.ts";
|
|
import { AdminOnly, createPermissionCheck, Permission as Per } from "../permission/permission.ts";
|
|
import { sendError } from "./error_handler.ts";
|
|
import { oshash } from "src/util/oshash.ts";
|
|
import { headComicPage, renderComicPage } from "./comic.ts";
|
|
|
|
export const getContentRouter = (controller: DocumentAccessor) => {
|
|
return new Elysia({
|
|
name: "content-router",
|
|
prefix: "/doc",
|
|
})
|
|
.get("/search", async ({ query }) => {
|
|
const limit = Math.min(Number(query.limit ?? 20), 100);
|
|
const option: QueryListOption = {
|
|
limit: limit,
|
|
allow_tag: query.allow_tag?.split(",") ?? [],
|
|
word: query.word,
|
|
cursor: query.cursor,
|
|
eager_loading: true,
|
|
offset: Number(query.offset),
|
|
use_offset: query.use_offset === 'true',
|
|
content_type: query.content_type,
|
|
};
|
|
return await controller.findList(option);
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
query: t.Object({
|
|
limit: t.Optional(t.String()),
|
|
cursor: t.Optional(t.Number()),
|
|
word: t.Optional(t.String()),
|
|
content_type: t.Optional(t.String()),
|
|
offset: t.Optional(t.Number()),
|
|
use_offset: t.Optional(t.String()),
|
|
allow_tag: t.Optional(t.String()),
|
|
})
|
|
})
|
|
.get("/_gid", async ({ query }) => {
|
|
const gid_list = query.gid.split(",").map(x => Number.parseInt(x));
|
|
if (gid_list.some(x => Number.isNaN(x)) || gid_list.length > 100) {
|
|
throw sendError(400, "Invalid GID list");
|
|
}
|
|
return await controller.findByGidList(gid_list);
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
query: t.Object({ gid: t.String() })
|
|
})
|
|
.get("/:num", async ({ params: { num } }) => {
|
|
const document = await controller.findById(num, true);
|
|
if (document === undefined) {
|
|
throw sendError(404, "document does not exist.");
|
|
}
|
|
return document;
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.post("/:num", async ({ params: { num }, body }) => {
|
|
const content_desc: Partial<Document> & { id: number } = {
|
|
id: num,
|
|
...body,
|
|
};
|
|
return await controller.update(content_desc);
|
|
}, {
|
|
beforeHandle: AdminOnly,
|
|
params: t.Object({ num: t.Numeric() }),
|
|
body: t.Object({}, { additionalProperties: true })
|
|
})
|
|
.delete("/:num", async ({ params: { num } }) => {
|
|
return await controller.del(num);
|
|
}, {
|
|
beforeHandle: AdminOnly,
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.get("/:num/similars", async ({ params: { num } }) => {
|
|
const doc = await controller.findById(num, true);
|
|
if (doc === undefined) {
|
|
throw sendError(404);
|
|
}
|
|
return await controller.getSimilarDocument(doc);
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.get("/:num/tags", async ({ params: { num } }) => {
|
|
const document = await controller.findById(num, true);
|
|
if (document === undefined) {
|
|
throw sendError(404, "document does not exist.");
|
|
}
|
|
return document.tags;
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.post("/:num/tags/:tag", async ({ params: { num, tag } }) => {
|
|
const doc = await controller.findById(num);
|
|
if (doc === undefined) {
|
|
throw sendError(404);
|
|
}
|
|
return await controller.addTag(doc, tag);
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.ModifyTag),
|
|
params: t.Object({ num: t.Numeric(), tag: t.String() })
|
|
})
|
|
.delete("/:num/tags/:tag", async ({ params: { num, tag } }) => {
|
|
const doc = await controller.findById(num);
|
|
if (doc === undefined) {
|
|
throw sendError(404);
|
|
}
|
|
return await controller.delTag(doc, tag);
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.ModifyTag),
|
|
params: t.Object({ num: t.Numeric(), tag: t.String() })
|
|
})
|
|
.post("/:num/_rehash", async ({ params: { num } }) => {
|
|
const doc = await controller.findById(num);
|
|
if (doc === undefined || doc.deleted_at !== null) {
|
|
throw sendError(404);
|
|
}
|
|
const filepath = join(doc.basepath, doc.filename);
|
|
try {
|
|
const new_hash = (await oshash(filepath)).toString();
|
|
return await controller.update({ id: num, content_hash: new_hash });
|
|
} catch (e) {
|
|
if ((e as NodeJS.ErrnoException).code === "ENOENT") {
|
|
throw sendError(404, "file not found");
|
|
}
|
|
throw e;
|
|
}
|
|
}, {
|
|
beforeHandle: AdminOnly,
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.post("/:num/_rescan", async ({ params: { num }, set }) => {
|
|
const doc = await controller.findById(num, true);
|
|
if (doc === undefined) {
|
|
throw sendError(404);
|
|
}
|
|
await controller.rescanDocument(doc);
|
|
set.status = 204; // No Content
|
|
}, {
|
|
beforeHandle: AdminOnly,
|
|
params: t.Object({ num: t.Numeric() })
|
|
})
|
|
.group("/:num", (app) =>
|
|
app
|
|
.derive(async ({ params: { num } }) => {
|
|
const docId = typeof num === "number" ? num : Number.parseInt(String(num));
|
|
if (Number.isNaN(docId)) {
|
|
throw sendError(400, "invalid document id");
|
|
}
|
|
const document = await controller.findById(docId, true);
|
|
if (document === undefined) {
|
|
throw sendError(404, "document does not exist.");
|
|
}
|
|
return { document, docId };
|
|
})
|
|
.head("/comic/thumbnail", async ({ document, request, set }) => {
|
|
if (document.content_type !== "comic") {
|
|
throw sendError(404);
|
|
}
|
|
const path = join(document.basepath, document.filename);
|
|
await headComicPage({
|
|
path,
|
|
page: 0,
|
|
reqHeaders: request.headers,
|
|
set,
|
|
});
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric() }),
|
|
})
|
|
.get("/comic/thumbnail", async ({ document, request, set }) => {
|
|
if (document.content_type !== "comic") {
|
|
throw sendError(404);
|
|
}
|
|
const path = join(document.basepath, document.filename);
|
|
const body = await renderComicPage({
|
|
path,
|
|
page: 0,
|
|
reqHeaders: request.headers,
|
|
set,
|
|
});
|
|
return body ?? undefined;
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric() }),
|
|
})
|
|
.head("/comic/:page", async ({ document, params: { page }, request, set }) => {
|
|
if (document.content_type !== "comic") {
|
|
throw sendError(404);
|
|
}
|
|
const pageIndex = page;
|
|
const path = join(document.basepath, document.filename);
|
|
await headComicPage({
|
|
path,
|
|
page: pageIndex,
|
|
reqHeaders: request.headers,
|
|
set,
|
|
});
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric(), page: t.Numeric() }),
|
|
})
|
|
.get("/comic/:page", async ({ document, params: { page }, request, set }) => {
|
|
if (document.content_type !== "comic") {
|
|
throw sendError(404);
|
|
}
|
|
const pageIndex = page;
|
|
const path = join(document.basepath, document.filename);
|
|
const body = await renderComicPage({
|
|
path,
|
|
page: pageIndex,
|
|
reqHeaders: request.headers,
|
|
set,
|
|
});
|
|
return body ?? undefined;
|
|
}, {
|
|
beforeHandle: createPermissionCheck(Per.QueryContent),
|
|
params: t.Object({ num: t.Numeric(), page: t.Numeric() }),
|
|
})
|
|
);
|
|
};
|
|
|
|
export default getContentRouter;
|