feat: add query with gid
This commit is contained in:
parent
a92ddadaf0
commit
18a137e441
@ -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>;
|
||||||
|
@ -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 {
|
||||||
|
@ -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>) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -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[];
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -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));
|
||||||
|
5
packages/server/src/util/gid.ts
Normal file
5
packages/server/src/util/gid.ts
Normal 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]);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user