feat: add query with gid

This commit is contained in:
monoid 2024-12-27 18:37:06 +09:00
parent a92ddadaf0
commit 18a137e441
11 changed files with 77 additions and 32 deletions

View File

@ -11,6 +11,7 @@ export const DocumentBodySchema = z.object({
additional: z.record(z.unknown()), additional: z.record(z.unknown()),
tags: z.array(z.string()), tags: z.array(z.string()),
pagenum: z.number().int(), pagenum: z.number().int(),
gid: z.number().nullable(),
}); });
export type DocumentBody = z.infer<typeof DocumentBodySchema>; export type DocumentBody = z.infer<typeof DocumentBodySchema>;

View File

@ -21,6 +21,7 @@ export interface Document {
modified_at: number; modified_at: number;
title: string; title: string;
pagenum: number; pagenum: number;
gid: number | null;
} }
export interface Permissions { export interface Permissions {

View File

@ -23,7 +23,7 @@ export async function up(db: Kysely<any>) {
.addColumn('filename', 'varchar(512)', col => col.notNull()) .addColumn('filename', 'varchar(512)', col => col.notNull())
.addColumn('content_hash', 'varchar') .addColumn('content_hash', 'varchar')
.addColumn('additional', 'json') .addColumn('additional', 'json')
.addColumn("pagenum", "integer", col => col.notNull()) .addColumn("pagenum", "integer", col => col.notNull())
.addColumn('created_at', 'integer', col => col.notNull()) .addColumn('created_at', 'integer', col => col.notNull())
.addColumn('modified_at', 'integer', col => col.notNull()) .addColumn('modified_at', 'integer', col => col.notNull())
.addColumn('deleted_at', 'integer') .addColumn('deleted_at', 'integer')
@ -62,13 +62,27 @@ export async function up(db: Kysely<any>) {
}) })
.execute(); .execute();
await db await db
.insertInto('schema_migration') .insertInto('schema_migration')
.values({ .values({
version: '0.0.1', version: '0.0.1',
dirty: false, dirty: false,
}) })
.execute(); .execute();
// create indexes
await db.schema.createIndex("index_document_basepath_filename")
.on("document")
.columns(["basepath", "filename"])
.execute();
await db.schema.createIndex("index_document_content_hash")
.on("document")
.columns(["content_hash"])
.execute();
await db.schema.createIndex("index_document_created_at")
.on("document")
.columns(["created_at"])
.execute();
} }
export async function down(db: Kysely<any>) { export async function down(db: Kysely<any>) {

View File

@ -3,6 +3,7 @@ import { promises, type Stats } from "node:fs";
import path, { extname } from "node:path"; import path, { extname } from "node:path";
import type { DocumentBody } from "dbtype"; import type { DocumentBody } from "dbtype";
import { oshash, OshashError } from "src/util/oshash.ts"; import { oshash, OshashError } from "src/util/oshash.ts";
import { extractGidFromFilename } from "src/util/gid.ts";
/** /**
* content file or directory referrer * content file or directory referrer
*/ */
@ -53,6 +54,7 @@ export const createDefaultClass = (type: string): ContentFileConstructor => {
content_hash: await this.getHash(), content_hash: await this.getHash(),
modified_at: await this.getMtime(), modified_at: await this.getMtime(),
pagenum: 0, pagenum: 0,
gid: extractGidFromFilename(base),
} as DocumentBody; } as DocumentBody;
return ret; return ret;
} }

View File

@ -88,7 +88,7 @@ class SqliteDocumentAccessor implements DocumentAccessor {
.executeTakeFirst() as { id: number }; .executeTakeFirst() as { id: number };
const id = id_lst.id; const id = id_lst.id;
if (tags.length > 0){ if (tags.length > 0) {
// add tags // add tags
await trx.insertInto("tags") await trx.insertInto("tags")
.values(tags.map((x) => ({ name: x }))) .values(tags.map((x) => ({ name: x })))
@ -202,6 +202,19 @@ class SqliteDocumentAccessor implements DocumentAccessor {
tags: JSON.parse(x.tags ?? "[]"), //?.map((x: { tag_name: string }) => x.tag_name) ?? [], tags: JSON.parse(x.tags ?? "[]"), //?.map((x: { tag_name: string }) => x.tag_name) ?? [],
})); }));
} }
async findByGidList(gid_list: number[]) {
const result = await this.kysely
.selectFrom("document")
.selectAll()
.where("gid", "in", gid_list)
.execute();
return result.map((x) => ({
...x,
content_hash: x.content_hash ?? "",
tags: [],
additional: {},
}));
}
async findByPath(path: string, filename?: string): Promise<Document[]> { async findByPath(path: string, filename?: string): Promise<Document[]> {
const results = await this.kysely const results = await this.kysely
.selectFrom("document") .selectFrom("document")
@ -216,6 +229,7 @@ class SqliteDocumentAccessor implements DocumentAccessor {
additional: {}, additional: {},
})); }));
} }
async update(c: Partial<Document> & { id: number }) { async update(c: Partial<Document> & { id: number }) {
const { id, tags, additional, ...rest } = c; const { id, tags, additional, ...rest } = c;
const r = await this.kysely.updateTable("document") const r = await this.kysely.updateTable("document")
@ -322,7 +336,7 @@ class SqliteDocumentAccessor implements DocumentAccessor {
.values(tags.map((x) => ({ tag_name: x, doc_id: c.id })) .values(tags.map((x) => ({ tag_name: x, doc_id: c.id }))
) )
.execute(); .execute();
}); });
} }
} }

View File

@ -25,11 +25,11 @@ export function getKysely() {
log: (event) => { log: (event) => {
if (event.level === "error") { if (event.level === "error") {
console.error("Query failed : ", { console.error("Query failed : ", {
durationMs: event.queryDurationMillis, durationMs: event.queryDurationMillis,
error: event.error, error: event.error,
sql: event.query.sql, sql: event.query.sql,
params: event.query.parameters, params: event.query.parameters,
}); });
} }
} }
}); });

View File

@ -52,7 +52,7 @@ export class ContentDiffHandler {
console.log("its not in waiting_list and db!!!: ", cpath); console.log("its not in waiting_list and db!!!: ", cpath);
return; return;
} }
console.log("deleted ",cpath, dbc[0].content_hash) console.log("deleted ",cpath, dbc[0].content_hash)
const content_hash = dbc[0].content_hash; const content_hash = dbc[0].content_hash;
// When a path is changed, it takes into account when the // When a path is changed, it takes into account when the
// creation event occurs first and the deletion occurs, not // creation event occurs first and the deletion occurs, not
@ -62,14 +62,12 @@ export class ContentDiffHandler {
if (cf) { if (cf) {
// if a path is changed, update the changed path. // if a path is changed, update the changed path.
console.log("update path from", cpath, "to", cf.path); console.log("update path from", cpath, "to", cf.path);
const newFilename = basename(cf.path); const new_doc = createContentFile(this.content_type, cf.path);
const newBasepath = dirname(cf.path);
this.waiting_list.deleteByHash(content_hash); this.waiting_list.deleteByHash(content_hash);
await this.doc_cntr.update({ await this.doc_cntr.update({
id: dbc[0].id, ...dbc[0],
deleted_at: null, ...new_doc.createDocumentBody(),
filename: newFilename,
basepath: newBasepath,
}); });
return; return;
} }
@ -91,21 +89,17 @@ export class ContentDiffHandler {
if (c !== undefined) { if (c !== undefined) {
await this.doc_cntr.update({ await this.doc_cntr.update({
id: c.id, id: c.id,
deleted_at: null, ...content.createDocumentBody(),
filename: filename,
basepath: basepath,
}); });
} }
if (this.waiting_list.hasByHash(hash)) { if (this.waiting_list.hasByHash(hash)) {
console.log("Hash Conflict!!!"); console.log("Hash Conflict!!!", cpath);
} }
this.waiting_list.set(content); this.waiting_list.set(content);
} }
private async OnChanged(prev_path: string, cur_path: string) { private async OnChanged(prev_path: string, cur_path: string) {
const prev_basepath = dirname(prev_path); const prev_basepath = dirname(prev_path);
const prev_filename = basename(prev_path); const prev_filename = basename(prev_path);
const cur_basepath = dirname(cur_path);
const cur_filename = basename(cur_path);
console.log("modify", cur_path, "from", prev_path); console.log("modify", cur_path, "from", prev_path);
const c = this.waiting_list.getByPath(prev_path); const c = this.waiting_list.getByPath(prev_path);
if (c !== undefined) { if (c !== undefined) {
@ -123,8 +117,7 @@ export class ContentDiffHandler {
await this.doc_cntr.update({ await this.doc_cntr.update({
...doc[0], ...doc[0],
basepath: cur_basepath, ...createContentFile(this.content_type, cur_path).createDocumentBody(),
filename: cur_filename,
}); });
} }
async onModified(path: string) { async onModified(path: string) {
@ -146,7 +139,7 @@ export class ContentDiffHandler {
// if it is in db, update it. // if it is in db, update it.
await this.doc_cntr.update({ await this.doc_cntr.update({
...doc[0], ...doc[0],
content_hash: await content.getHash(), ...content.createDocumentBody(),
modified_at: Date.now(), modified_at: Date.now(),
}); });
} }

View File

@ -1,5 +1,5 @@
import { ConfigManager } from "../../util/configRW.ts"; import { ConfigManager } from "../../util/configRW.ts";
import ComicSchema from "./ComicConfig.schema.json"; import ComicSchema from "./ComicConfig.schema.json" assert { type: "json" };
export interface ComicConfig { export interface ComicConfig {
watch: string[]; watch: string[];
} }

View File

@ -55,6 +55,12 @@ export interface DocumentAccessor {
* if you call this function with filename, its return array length is 0 or 1. * if you call this function with filename, its return array length is 0 or 1.
*/ */
findByPath: (basepath: string, filename?: string) => Promise<Document[]>; findByPath: (basepath: string, filename?: string) => Promise<Document[]>;
/**
* find by gid list
* @param gid_list
* @returns Document list
*/
findByGidList: (gid_list: number[]) => Promise<Document[]>;
/** /**
* find deleted content * find deleted content
*/ */

View File

@ -213,6 +213,15 @@ function getRescanDocumentHandler(controller: DocumentAccessor) {
export const getContentRouter = (controller: DocumentAccessor) => { export const getContentRouter = (controller: DocumentAccessor) => {
const ret = new Router(); const ret = new Router();
ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller)); ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller));
ret.get("/_gid", PerCheck(Per.QueryContent), async (ctx, next) => {
const gid_list = ParseQueryArray(ctx.query.gid).map((x) => Number.parseInt(x))
if (gid_list.some((x) => Number.isNaN(x))) {
return sendError(400, "gid is not a number");
}
const r = await controller.findByGidList(gid_list);
ctx.body = r;
ctx.type = "json";
});
ret.get("/:num(\\d+)", PerCheck(Per.QueryContent), ContentIDHandler(controller)); ret.get("/:num(\\d+)", PerCheck(Per.QueryContent), ContentIDHandler(controller));
ret.all("/:num(\\d+)/(.*)", PerCheck(Per.QueryContent), ContentHandler(controller)); ret.all("/:num(\\d+)/(.*)", PerCheck(Per.QueryContent), ContentHandler(controller));
ret.post("/:num(\\d+)", AdminOnly, UpdateContentHandler(controller)); ret.post("/:num(\\d+)", AdminOnly, UpdateContentHandler(controller));

View File

@ -0,0 +1,5 @@
export function extractGidFromFilename(filename: string) {
const match = filename.match(/^([0-9]+)(:?\.|\s)/);
if (match === null) return null;
return Number.parseInt(match[1]);
}