diff options
| author | MohamedBassem <me@mbassem.com> | 2024-03-01 23:17:27 +0000 |
|---|---|---|
| committer | MohamedBassem <me@mbassem.com> | 2024-03-01 23:19:03 +0000 |
| commit | 7ddcfb630d3dec3d9fecbfd6a498ca7c572809ec (patch) | |
| tree | c0339e9181b35d0819bc0bfd1219ccdb262e54d2 /packages/web/server/api | |
| parent | a5434730ede1272f195d6a4b13207b840a5ac2cf (diff) | |
| download | karakeep-7ddcfb630d3dec3d9fecbfd6a498ca7c572809ec.tar.zst | |
feature: Add an admin page showing server stats and actions
Diffstat (limited to 'packages/web/server/api')
| -rw-r--r-- | packages/web/server/api/routers/_app.ts | 2 | ||||
| -rw-r--r-- | packages/web/server/api/routers/admin.ts | 86 |
2 files changed, 88 insertions, 0 deletions
diff --git a/packages/web/server/api/routers/_app.ts b/packages/web/server/api/routers/_app.ts index 6a1b05e9..43ab6f5d 100644 --- a/packages/web/server/api/routers/_app.ts +++ b/packages/web/server/api/routers/_app.ts @@ -1,4 +1,5 @@ import { router } from "../trpc"; +import { adminAppRouter } from "./admin"; import { apiKeysAppRouter } from "./apiKeys"; import { bookmarksAppRouter } from "./bookmarks"; import { listsAppRouter } from "./lists"; @@ -8,6 +9,7 @@ export const appRouter = router({ apiKeys: apiKeysAppRouter, users: usersAppRouter, lists: listsAppRouter, + admin: adminAppRouter, }); // export type definition of API export type AppRouter = typeof appRouter; diff --git a/packages/web/server/api/routers/admin.ts b/packages/web/server/api/routers/admin.ts new file mode 100644 index 00000000..92769151 --- /dev/null +++ b/packages/web/server/api/routers/admin.ts @@ -0,0 +1,86 @@ +import { authedProcedure, router } from "../trpc"; +import { z } from "zod"; +import { TRPCError } from "@trpc/server"; +import { count } from "drizzle-orm"; +import { bookmarks, users } from "@hoarder/db/schema"; +import { + LinkCrawlerQueue, + OpenAIQueue, + SearchIndexingQueue, +} from "@hoarder/shared/queues"; + +const adminProcedure = authedProcedure.use(function isAdmin(opts) { + const user = opts.ctx.user; + if (user.role != "admin") { + throw new TRPCError({ code: "FORBIDDEN" }); + } + return opts.next(opts); +}); + +export const adminAppRouter = router({ + stats: adminProcedure + .output( + z.object({ + numUsers: z.number(), + numBookmarks: z.number(), + pendingCrawls: z.number(), + pendingIndexing: z.number(), + pendingOpenai: z.number(), + }), + ) + .query(async ({ ctx }) => { + const [ + [{ value: numUsers }], + [{ value: numBookmarks }], + pendingCrawls, + pendingIndexing, + pendingOpenai, + ] = await Promise.all([ + ctx.db.select({ value: count() }).from(users), + ctx.db.select({ value: count() }).from(bookmarks), + LinkCrawlerQueue.getWaitingCount(), + SearchIndexingQueue.getWaitingCount(), + OpenAIQueue.getWaitingCount(), + ]); + + return { + numUsers, + numBookmarks, + pendingCrawls, + pendingIndexing, + pendingOpenai, + }; + }), + recrawlAllLinks: adminProcedure.mutation(async ({ ctx }) => { + const bookmarkIds = await ctx.db.query.bookmarkLinks.findMany({ + columns: { + id: true, + }, + }); + + await Promise.all( + bookmarkIds.map((b) => + LinkCrawlerQueue.add("crawl", { + bookmarkId: b.id, + }), + ), + ); + }), + + reindexAllBookmarks: adminProcedure.mutation(async ({ ctx }) => { + const bookmarkIds = await ctx.db.query.bookmarks.findMany({ + columns: { + id: true, + }, + }); + + await Promise.all( + bookmarkIds.map((b) => + SearchIndexingQueue.add("search_indexing", { + bookmarkId: b.id, + type: "index", + }), + ), + ); + }), +}); |
