feat: rescan document
This commit is contained in:
parent
e00c888d7b
commit
0d3128948b
@ -16,6 +16,16 @@ export function useRehashDoc() {
|
||||
};
|
||||
}
|
||||
|
||||
export function useRescanDoc() {
|
||||
const { mutate } = useSWRConfig();
|
||||
return async (id: string) => {
|
||||
await fetch(`/api/doc/${id}/_rescan`, {
|
||||
method: "POST",
|
||||
});
|
||||
mutate(`/api/doc/${id}`);
|
||||
};
|
||||
}
|
||||
|
||||
export function useGalleryDocSimilar(id: string) {
|
||||
return useSWR<Document[]>(`/api/doc/${id}/similars`, fetcher);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useGalleryDoc, useGalleryDocSimilar, useRehashDoc } from "../hook/useGalleryDoc.ts";
|
||||
import { useGalleryDoc, useGalleryDocSimilar, useRehashDoc, useRescanDoc } from "../hook/useGalleryDoc.ts";
|
||||
import TagBadge from "@/components/gallery/TagBadge";
|
||||
import StyledLink from "@/components/gallery/StyledLink";
|
||||
import { Link, useLocation } from "wouter";
|
||||
@ -8,6 +8,7 @@ import { DescTagItem, DescItem } from "../components/gallery/DescItem.tsx";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import { GalleryCard } from "@/components/gallery/GalleryCard.tsx";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useLogin } from "@/state/user.ts";
|
||||
|
||||
export interface ContentInfoPageProps {
|
||||
params: {
|
||||
@ -35,6 +36,10 @@ function Wrapper({ children }: { children: React.ReactNode }) {
|
||||
export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||
const { data, error, isLoading } = useGalleryDoc(params.id);
|
||||
const rehashDoc = useRehashDoc();
|
||||
const rescanDoc = useRescanDoc();
|
||||
const user = useLogin();
|
||||
const username = user?.username;
|
||||
const isAdmin = username === "admin";
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-4 flex items-center justify-center h-full">
|
||||
@ -69,12 +74,21 @@ export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||
</div>
|
||||
</Link>
|
||||
<Card className="flex-1 relative">
|
||||
<div className="absolute top-0 right-0 p-2">
|
||||
<Button variant="ghost" onClick={async () => {
|
||||
// Rehash
|
||||
await rehashDoc(params.id);
|
||||
}}>Rehash</Button>
|
||||
</div>
|
||||
{isAdmin &&
|
||||
<div className="absolute top-0 right-0 p-2 grid">
|
||||
<Button variant="ghost" onClick={async () => {
|
||||
// Rehash
|
||||
await rehashDoc(params.id);
|
||||
}}>Rehash</Button>
|
||||
<Button variant="ghost" onClick={async () => {
|
||||
if (!window.confirm("Are you sure?")) {
|
||||
return;
|
||||
}
|
||||
// Rescan
|
||||
await rescanDoc(params.id);
|
||||
}}>Rescan</Button>
|
||||
</div>
|
||||
}
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<StyledLink to={contentLocation}>
|
||||
|
@ -9,6 +9,8 @@ import type {
|
||||
} from "dbtype";
|
||||
import type { NotNull } from "kysely";
|
||||
import { MyParseJSONResultsPlugin } from "./plugin.ts";
|
||||
import { getContentFileConstructor } from "src/content/file.ts";
|
||||
import { join } from "path";
|
||||
|
||||
type DBDocument = db.Document;
|
||||
|
||||
@ -280,6 +282,45 @@ class SqliteDocumentAccessor implements DocumentAccessor {
|
||||
additional: {},
|
||||
}));
|
||||
}
|
||||
|
||||
async rescanDocument(c: Document) {
|
||||
return this.kysely.transaction().execute(async (trx) => {
|
||||
const ContentFile = getContentFileConstructor(c.content_type);
|
||||
if (ContentFile === undefined) {
|
||||
throw new Error(`${c.content_type} is not defined.`);
|
||||
}
|
||||
const path = join(c.basepath, c.filename);
|
||||
const content = new ContentFile(path);
|
||||
const body = await content.createDocumentBody();
|
||||
|
||||
const { tags, ...rest } = body;
|
||||
|
||||
// update document
|
||||
await trx.updateTable("document")
|
||||
.set({
|
||||
...rest,
|
||||
modified_at: Date.now(),
|
||||
additional: JSON.stringify(body.additional),
|
||||
})
|
||||
.where("id", "=", c.id)
|
||||
.execute();
|
||||
|
||||
// delete old tags
|
||||
await trx.deleteFrom("doc_tag_relation")
|
||||
.where("doc_id", "=", c.id)
|
||||
.execute();
|
||||
// update tags and doc_tag_relation
|
||||
await trx.insertInto("tags")
|
||||
.values(tags.map((x) => ({ name: x })))
|
||||
.onConflict((oc) => oc.doNothing())
|
||||
.execute();
|
||||
await trx.insertInto("doc_tag_relation")
|
||||
.values(tags.map((x) => ({ tag_name: x, doc_id: c.id }))
|
||||
)
|
||||
.execute();
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
export const createSqliteDocumentAccessor = (kysely = getKysely()): DocumentAccessor => {
|
||||
return new SqliteDocumentAccessor(kysely);
|
||||
|
@ -36,6 +36,11 @@ export const isDoc = (c: unknown): c is Document => {
|
||||
};
|
||||
|
||||
export interface DocumentAccessor {
|
||||
/**
|
||||
* rescan document
|
||||
* it will update document's metadata and tags from file.
|
||||
*/
|
||||
rescanDocument(c: Document): Promise<void>;
|
||||
/**
|
||||
* find list by option
|
||||
* @returns documents list
|
||||
|
@ -197,6 +197,19 @@ function getSimilarDocumentHandler(controller: DocumentAccessor) {
|
||||
};
|
||||
}
|
||||
|
||||
function getRescanDocumentHandler(controller: DocumentAccessor) {
|
||||
return async (ctx: Context, next: Next) => {
|
||||
const num = Number.parseInt(ctx.params.num);
|
||||
const c = await controller.findById(num, true);
|
||||
if (c === undefined) {
|
||||
return sendError(404);
|
||||
}
|
||||
await controller.rescanDocument(c);
|
||||
// 204 No Content
|
||||
ctx.status = 204;
|
||||
};
|
||||
}
|
||||
|
||||
export const getContentRouter = (controller: DocumentAccessor) => {
|
||||
const ret = new Router();
|
||||
ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller));
|
||||
@ -211,6 +224,7 @@ export const getContentRouter = (controller: DocumentAccessor) => {
|
||||
ret.del("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), DelTagHandler(controller));
|
||||
ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), new AllContentRouter().routes());
|
||||
ret.post("/:num(\\d+)/_rehash", AdminOnly, RehashContentHandler(controller));
|
||||
ret.post("/:num(\\d+)/_rescan", AdminOnly, getRescanDocumentHandler(controller));
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user