Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
/**
|
|
* Page Router — CRUD, tree operations
|
|
*/
|
|
|
|
import { TRPCError } from '@trpc/server';
|
|
import { z } from 'zod';
|
|
import type { ProcedureBuilder } from '../trpc.js';
|
|
import { requirePageRepository } from './_helpers.js';
|
|
|
|
export function pageProcedures(publicProcedure: ProcedureBuilder) {
|
|
return {
|
|
getRootPage: publicProcedure
|
|
.input(z.object({ initiativeId: z.string().min(1) }))
|
|
.query(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
return repo.getOrCreateRootPage(input.initiativeId);
|
|
}),
|
|
|
|
getPage: publicProcedure
|
|
.input(z.object({ id: z.string().min(1) }))
|
|
.query(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
const page = await repo.findById(input.id);
|
|
if (!page) {
|
|
throw new TRPCError({
|
|
code: 'NOT_FOUND',
|
|
message: `Page '${input.id}' not found`,
|
|
});
|
|
}
|
|
return page;
|
|
}),
|
|
|
|
getPageUpdatedAtMap: publicProcedure
|
|
.input(z.object({ ids: z.array(z.string().min(1)) }))
|
|
.query(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
const foundPages = await repo.findByIds(input.ids);
|
|
const map: Record<string, string> = {};
|
|
for (const p of foundPages) {
|
|
map[p.id] = p.updatedAt instanceof Date ? p.updatedAt.toISOString() : String(p.updatedAt);
|
|
}
|
|
return map;
|
|
}),
|
|
|
|
listPages: publicProcedure
|
|
.input(z.object({ initiativeId: z.string().min(1) }))
|
|
.query(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
return repo.findByInitiativeId(input.initiativeId);
|
|
}),
|
|
|
|
listChildPages: publicProcedure
|
|
.input(z.object({ parentPageId: z.string().min(1) }))
|
|
.query(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
return repo.findByParentPageId(input.parentPageId);
|
|
}),
|
|
|
|
createPage: publicProcedure
|
|
.input(z.object({
|
|
initiativeId: z.string().min(1),
|
|
parentPageId: z.string().min(1).nullable(),
|
|
title: z.string().min(1),
|
|
}))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
const page = await repo.create({
|
|
initiativeId: input.initiativeId,
|
|
parentPageId: input.parentPageId,
|
|
title: input.title,
|
|
content: null,
|
|
sortOrder: 0,
|
|
});
|
|
ctx.eventBus.emit({
|
|
type: 'page:created',
|
|
timestamp: new Date(),
|
|
payload: { pageId: page.id, initiativeId: input.initiativeId, title: input.title },
|
|
});
|
|
return page;
|
|
}),
|
|
|
|
updatePage: publicProcedure
|
|
.input(z.object({
|
|
id: z.string().min(1),
|
|
title: z.string().min(1).optional(),
|
|
content: z.string().nullable().optional(),
|
|
sortOrder: z.number().int().optional(),
|
|
}))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
const { id, ...data } = input;
|
|
const page = await repo.update(id, data);
|
|
ctx.eventBus.emit({
|
|
type: 'page:updated',
|
|
timestamp: new Date(),
|
|
payload: { pageId: id, initiativeId: page.initiativeId, title: input.title },
|
|
});
|
|
return page;
|
|
}),
|
|
|
|
deletePage: publicProcedure
|
|
.input(z.object({ id: z.string().min(1) }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const repo = requirePageRepository(ctx);
|
|
const page = await repo.findById(input.id);
|
|
await repo.delete(input.id);
|
|
if (page) {
|
|
ctx.eventBus.emit({
|
|
type: 'page:deleted',
|
|
timestamp: new Date(),
|
|
payload: { pageId: input.id, initiativeId: page.initiativeId },
|
|
});
|
|
}
|
|
return { success: true };
|
|
}),
|
|
};
|
|
}
|