- Replace in-memory Maps with Drizzle ORM queries for all CRUD operations - Use integer IDs matching SQLite schema instead of UUIDs - Fix scriptId to projectId inconsistency in characters and scenes - Add project ownership verification on all mutation procedures - Make getCharacter/getScene procedures protected (not public) - Proper JWT-based userId validation via context - Add cascade delete for characters/relationships/scenes on project deletion - Add verifyProjectOwnership helper for authorization checks - Rewrite tests with createCallerFactory pattern for tRPC v11 - Use better-sqlite3 for in-memory test database - Split vitest config into separate file from vite config
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import { initTRPC, TRPCError } from '@trpc/server';
|
|
import { z } from 'zod';
|
|
import type { TRPCContext } from './types';
|
|
|
|
// Initialize tRPC with context
|
|
const t = initTRPC.context<TRPCContext>().create();
|
|
|
|
// Middleware for authentication
|
|
const isAuthenticated = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.userId) {
|
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'User not authenticated' });
|
|
}
|
|
return next({ ctx: { ...ctx, userId: ctx.userId } });
|
|
});
|
|
|
|
// Middleware for database access
|
|
const hasDb = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.db) {
|
|
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Database not available' });
|
|
}
|
|
return next({ ctx: { ...ctx, db: ctx.db } });
|
|
});
|
|
|
|
// Base router
|
|
export const baseRouter = t.router;
|
|
|
|
// Procedure builders
|
|
export const publicProcedure = t.procedure.use(hasDb);
|
|
export const protectedProcedure = t.procedure.use(isAuthenticated).use(hasDb);
|
|
const hasProjectAccess = t.middleware(({ ctx, next }) => {
|
|
if (!ctx.projectId) {
|
|
throw new TRPCError({ code: 'FORBIDDEN', message: 'Project access required' });
|
|
}
|
|
return next({ ctx: { ...ctx, projectId: ctx.projectId } });
|
|
});
|
|
|
|
export const projectProcedure = t.procedure
|
|
.use(isAuthenticated)
|
|
.use(hasDb)
|
|
.use(hasProjectAccess);
|
|
|
|
// Validation middleware
|
|
export const validateInput = <T extends z.ZodTypeAny>(schema: T) => {
|
|
return t.middleware(({ input, next }) => {
|
|
const validated = schema.parse(input);
|
|
return next({ input: validated });
|
|
});
|
|
};
|
|
|
|
export { t, TRPCError };
|