feat(11-04): add initiative tRPC procedures

- Add requireInitiativeRepository helper function
- Add createInitiative mutation procedure
- Add listInitiatives query with optional status filter
- Add getInitiative query with NOT_FOUND error handling
- Add updateInitiative mutation procedure
This commit is contained in:
Lukas May
2026-01-31 19:11:23 +01:00
parent 75f8887b53
commit 4d42da9496

View File

@@ -11,8 +11,11 @@ import type { TRPCContext } from './context.js';
import type { AgentInfo, AgentResult } from '../agent/types.js';
import type { TaskRepository } from '../db/repositories/task-repository.js';
import type { MessageRepository } from '../db/repositories/message-repository.js';
import type { InitiativeRepository } from '../db/repositories/initiative-repository.js';
import type { PhaseRepository } from '../db/repositories/phase-repository.js';
import type { DispatchManager } from '../dispatch/types.js';
import type { CoordinationManager } from '../coordination/types.js';
import type { Phase } from '../db/schema.js';
/**
* Initialize tRPC with our context type.
@@ -232,6 +235,32 @@ function requireCoordinationManager(ctx: TRPCContext): CoordinationManager {
return ctx.coordinationManager;
}
/**
* Helper to ensure initiativeRepository is available in context.
*/
function requireInitiativeRepository(ctx: TRPCContext): InitiativeRepository {
if (!ctx.initiativeRepository) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Initiative repository not available',
});
}
return ctx.initiativeRepository;
}
/**
* Helper to ensure phaseRepository is available in context.
*/
function requirePhaseRepository(ctx: TRPCContext): PhaseRepository {
if (!ctx.phaseRepository) {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Phase repository not available',
});
}
return ctx.phaseRepository;
}
// =============================================================================
// Application Router with Procedures
// =============================================================================
@@ -633,6 +662,79 @@ export const appRouter = router({
const coordinationManager = requireCoordinationManager(ctx);
return coordinationManager.getNextMergeable();
}),
// ===========================================================================
// Initiative Procedures
// ===========================================================================
/**
* Create a new initiative.
* Returns the created initiative with generated ID.
*/
createInitiative: publicProcedure
.input(z.object({
name: z.string().min(1),
description: z.string().optional(),
}))
.mutation(async ({ ctx, input }) => {
const repo = requireInitiativeRepository(ctx);
return repo.create({
name: input.name,
description: input.description ?? null,
status: 'active',
});
}),
/**
* List all initiatives with optional status filter.
* Returns initiatives ordered by creation time.
*/
listInitiatives: publicProcedure
.input(z.object({
status: z.enum(['active', 'completed', 'archived']).optional(),
}).optional())
.query(async ({ ctx, input }) => {
const repo = requireInitiativeRepository(ctx);
if (input?.status) {
return repo.findByStatus(input.status);
}
return repo.findAll();
}),
/**
* Get an initiative by ID.
* Throws NOT_FOUND if initiative doesn't exist.
*/
getInitiative: publicProcedure
.input(z.object({ id: z.string().min(1) }))
.query(async ({ ctx, input }) => {
const repo = requireInitiativeRepository(ctx);
const initiative = await repo.findById(input.id);
if (!initiative) {
throw new TRPCError({
code: 'NOT_FOUND',
message: `Initiative '${input.id}' not found`,
});
}
return initiative;
}),
/**
* Update an initiative.
* Returns the updated initiative.
*/
updateInitiative: publicProcedure
.input(z.object({
id: z.string().min(1),
name: z.string().min(1).optional(),
description: z.string().optional(),
status: z.enum(['active', 'completed', 'archived']).optional(),
}))
.mutation(async ({ ctx, input }) => {
const repo = requireInitiativeRepository(ctx);
const { id, ...data } = input;
return repo.update(id, data);
}),
});
/**