FRE-592: Fix remaining code review blockers and add tests

- 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
This commit is contained in:
2026-04-24 08:31:42 -04:00
parent 4d9b4ecf2a
commit 79d153f75a
11 changed files with 443 additions and 352 deletions

View File

@@ -13,22 +13,31 @@ const isAuthenticated = t.middleware(({ ctx, next }) => {
return next({ ctx: { ...ctx, userId: ctx.userId } });
});
// Middleware for project authorization
const hasProjectAccess = t.middleware(({ ctx, next }) => {
const projectId = ctx.projectId;
if (!projectId) {
throw new TRPCError({ code: 'FORBIDDEN', message: 'Project access required' });
// 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, projectId } });
return next({ ctx: { ...ctx, db: ctx.db } });
});
// Base router
export const baseRouter = t.router;
// Procedure builders
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthenticated);
export const projectProcedure = t.procedure.use(hasProjectAccess);
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) => {