feat(trpc): media router mount/library/scrape/playback/danmaku

This commit is contained in:
noelorin 2026-09-25 03:06:12 +08:00
parent 8839763f24
commit f009d2cce4
4 changed files with 109 additions and 0 deletions

View File

@ -2,6 +2,7 @@
// Router // Router
export { appRouter } from "./router/index.js"; export { appRouter } from "./router/index.js";
export type { AppRouter, RouterInputs, RouterOutputs, TrpcContext } from "./router/index.js"; export type { AppRouter, RouterInputs, RouterOutputs, TrpcContext } from "./router/index.js";
export { mediaRouter } from "./router/media.router.js";
// Service layer — usable without tRPC (tests, scripts) // Service layer — usable without tRPC (tests, scripts)
export { userService } from "./services/user.service.js"; export { userService } from "./services/user.service.js";

View File

@ -8,6 +8,7 @@ import { userSchemas } from "@app/types";
import { userService } from "../services/user.service.js"; import { userService } from "../services/user.service.js";
import type { TrpcContext } from "../context.js"; import type { TrpcContext } from "../context.js";
import { authRouter } from "./auth.router.js"; import { authRouter } from "./auth.router.js";
import { mediaRouter } from "./media.router.js";
// ─── User router ──────────────────────────────────────────────────────── // ─── User router ────────────────────────────────────────────────────────
export const userRouter = t.router({ export const userRouter = t.router({
@ -34,6 +35,7 @@ export const userRouter = t.router({
export const appRouter = t.router({ export const appRouter = t.router({
user: userRouter, user: userRouter,
auth: authRouter, auth: authRouter,
media: mediaRouter,
}); });
// ─── Type re-exports ──────────────────────────────────────────────────── // ─── Type re-exports ────────────────────────────────────────────────────

View File

@ -0,0 +1,85 @@
import {
danmakuSchemas,
librarySchemas,
mountSchemas,
playbackSchemas,
scrapeSchemas,
} from "@app/types";
import { t } from "../context.js";
import { danmakuService } from "../services/danmaku.service.js";
import { libraryService } from "../services/library.service.js";
import { mountService } from "../services/mount.service.js";
import { playbackService } from "../services/playback.service.js";
import { protectedProcedure } from "../services/procedure.js";
import { scrapeService } from "../services/scrape.service.js";
export const mediaRouter = t.router({
mountList: protectedProcedure
.input(mountSchemas.list)
.query(({ ctx }) => mountService.list(ctx.userId)),
mountCreate: protectedProcedure
.input(mountSchemas.create)
.mutation(({ ctx, input }) => mountService.create(ctx.userId, input)),
mountUpdate: protectedProcedure
.input(mountSchemas.update)
.mutation(({ ctx, input }) => mountService.update(ctx.userId, input)),
mountDelete: protectedProcedure
.input(mountSchemas.delete)
.mutation(({ ctx, input }) => mountService.delete(ctx.userId, input.id)),
mountTest: protectedProcedure
.input(mountSchemas.test)
.mutation(({ input }) => mountService.test(input)),
mountListDir: protectedProcedure
.input(mountSchemas.listDir)
.query(({ ctx, input }) => mountService.listDir(ctx.userId, input)),
libraryList: protectedProcedure
.input(librarySchemas.list)
.query(({ ctx, input }) => libraryService.list(ctx.userId, input)),
libraryGet: protectedProcedure
.input(librarySchemas.get)
.query(({ ctx, input }) => libraryService.get(ctx.userId, input.id)),
libraryScan: protectedProcedure
.input(librarySchemas.scan)
.mutation(({ ctx, input }) => libraryService.scan(ctx.userId, input.mountId)),
scrapeSearch: protectedProcedure
.input(scrapeSchemas.search)
.query(({ input }) => scrapeService.search(input.q)),
scrapeBind: protectedProcedure
.input(scrapeSchemas.bind)
.mutation(({ ctx, input }) =>
scrapeService.bind(ctx.userId, input.mediaItemId, input.bangumiId),
),
scrapeRescrape: protectedProcedure
.input(scrapeSchemas.rescrape)
.mutation(({ ctx, input }) =>
scrapeService
.autoScrape(ctx.userId, input.mediaItemId)
.then(() => ({ success: true as const })),
),
playbackReport: protectedProcedure
.input(playbackSchemas.report)
.mutation(({ ctx, input }) => playbackService.report(ctx.userId, input)),
playbackGet: protectedProcedure
.input(playbackSchemas.get)
.query(({ ctx, input }) => playbackService.get(ctx.userId, input.mediaItemId)),
danmakuFetch: protectedProcedure
.input(danmakuSchemas.fetch)
.query(({ ctx, input }) => danmakuService.fetch(ctx.userId, input.mediaItemId)),
danmakuImportMeta: protectedProcedure
.input(danmakuSchemas.importMeta)
.mutation(({ ctx, input }) =>
danmakuService.importMeta(ctx.userId, input.mediaItemId, input.byteSize),
),
danmakuGetSettings: protectedProcedure.input(danmakuSchemas.getSettings).query(() => ({
enabled: true,
opacity: 0.8,
density: 1,
})),
danmakuSaveSettings: protectedProcedure
.input(danmakuSchemas.settings)
.mutation(({ input }) => input),
});

View File

@ -0,0 +1,21 @@
import { TRPCError } from "@trpc/server";
import { t } from "../context.js";
import { authService } from "./auth.service.js";
/** 需要登录;解析出 userId 注入 ctx。 */
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.sessionToken) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "请先登录" });
}
const user = await authService.getSessionUser(ctx.sessionToken);
if (!user) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "请先登录" });
}
return next({
ctx: {
sessionToken: ctx.sessionToken,
setCookie: ctx.setCookie,
userId: user.id,
},
});
});