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) {
|
export function useGalleryDocSimilar(id: string) {
|
||||||
return useSWR<Document[]>(`/api/doc/${id}/similars`, fetcher);
|
return useSWR<Document[]>(`/api/doc/${id}/similars`, fetcher);
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
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 TagBadge from "@/components/gallery/TagBadge";
|
||||||
import StyledLink from "@/components/gallery/StyledLink";
|
import StyledLink from "@/components/gallery/StyledLink";
|
||||||
import { Link, useLocation } from "wouter";
|
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 { Button } from "@/components/ui/button.tsx";
|
||||||
import { GalleryCard } from "@/components/gallery/GalleryCard.tsx";
|
import { GalleryCard } from "@/components/gallery/GalleryCard.tsx";
|
||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
|
import { useLogin } from "@/state/user.ts";
|
||||||
|
|
||||||
export interface ContentInfoPageProps {
|
export interface ContentInfoPageProps {
|
||||||
params: {
|
params: {
|
||||||
@ -35,6 +36,10 @@ function Wrapper({ children }: { children: React.ReactNode }) {
|
|||||||
export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||||
const { data, error, isLoading } = useGalleryDoc(params.id);
|
const { data, error, isLoading } = useGalleryDoc(params.id);
|
||||||
const rehashDoc = useRehashDoc();
|
const rehashDoc = useRehashDoc();
|
||||||
|
const rescanDoc = useRescanDoc();
|
||||||
|
const user = useLogin();
|
||||||
|
const username = user?.username;
|
||||||
|
const isAdmin = username === "admin";
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <div className="p-4 flex items-center justify-center h-full">
|
return <div className="p-4 flex items-center justify-center h-full">
|
||||||
@ -69,12 +74,21 @@ export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<Card className="flex-1 relative">
|
<Card className="flex-1 relative">
|
||||||
<div className="absolute top-0 right-0 p-2">
|
{isAdmin &&
|
||||||
|
<div className="absolute top-0 right-0 p-2 grid">
|
||||||
<Button variant="ghost" onClick={async () => {
|
<Button variant="ghost" onClick={async () => {
|
||||||
// Rehash
|
// Rehash
|
||||||
await rehashDoc(params.id);
|
await rehashDoc(params.id);
|
||||||
}}>Rehash</Button>
|
}}>Rehash</Button>
|
||||||
|
<Button variant="ghost" onClick={async () => {
|
||||||
|
if (!window.confirm("Are you sure?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Rescan
|
||||||
|
await rescanDoc(params.id);
|
||||||
|
}}>Rescan</Button>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
<StyledLink to={contentLocation}>
|
<StyledLink to={contentLocation}>
|
||||||
|
@ -9,6 +9,8 @@ import type {
|
|||||||
} from "dbtype";
|
} from "dbtype";
|
||||||
import type { NotNull } from "kysely";
|
import type { NotNull } from "kysely";
|
||||||
import { MyParseJSONResultsPlugin } from "./plugin.ts";
|
import { MyParseJSONResultsPlugin } from "./plugin.ts";
|
||||||
|
import { getContentFileConstructor } from "src/content/file.ts";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
type DBDocument = db.Document;
|
type DBDocument = db.Document;
|
||||||
|
|
||||||
@ -280,6 +282,45 @@ class SqliteDocumentAccessor implements DocumentAccessor {
|
|||||||
additional: {},
|
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 => {
|
export const createSqliteDocumentAccessor = (kysely = getKysely()): DocumentAccessor => {
|
||||||
return new SqliteDocumentAccessor(kysely);
|
return new SqliteDocumentAccessor(kysely);
|
||||||
|
@ -36,6 +36,11 @@ export const isDoc = (c: unknown): c is Document => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface DocumentAccessor {
|
export interface DocumentAccessor {
|
||||||
|
/**
|
||||||
|
* rescan document
|
||||||
|
* it will update document's metadata and tags from file.
|
||||||
|
*/
|
||||||
|
rescanDocument(c: Document): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* find list by option
|
* find list by option
|
||||||
* @returns documents list
|
* @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) => {
|
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));
|
||||||
@ -211,6 +224,7 @@ export const getContentRouter = (controller: DocumentAccessor) => {
|
|||||||
ret.del("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), DelTagHandler(controller));
|
ret.del("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), DelTagHandler(controller));
|
||||||
ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), new AllContentRouter().routes());
|
ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), new AllContentRouter().routes());
|
||||||
ret.post("/:num(\\d+)/_rehash", AdminOnly, RehashContentHandler(controller));
|
ret.post("/:num(\\d+)/_rehash", AdminOnly, RehashContentHandler(controller));
|
||||||
|
ret.post("/:num(\\d+)/_rescan", AdminOnly, getRescanDocumentHandler(controller));
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user