Files
CapaKraken/packages/api/src/router/dashboard.ts
T
Hartmut cd78f72f33 chore: full technical rename planarchy → capakraken
Complete rename of all technical identifiers across the codebase:

Package names (11 packages):
- @planarchy/* → @capakraken/* in all package.json, tsconfig, imports

Import statements: 277 files, 548 occurrences replaced

Database & Docker:
- PostgreSQL user/db: planarchy → capakraken
- Docker volumes: planarchy_pgdata → capakraken_pgdata
- Connection strings updated in docker-compose, .env, CI

CI/CD:
- GitHub Actions workflow: all filter commands updated
- Test database credentials updated

Infrastructure:
- Redis channel: planarchy:sse → capakraken:sse
- Logger service name: planarchy-api → capakraken-api
- Anonymization seed updated
- Start/stop/restart scripts updated

Test data:
- Seed emails: @planarchy.dev → @capakraken.dev
- E2E test credentials: all 11 spec files updated
- Email defaults: @planarchy.app → @capakraken.app
- localStorage keys: planarchy_* → capakraken_*

Documentation: 30+ .md files updated

Verification:
- pnpm install: workspace resolution works
- TypeScript: only pre-existing TS2589 (no new errors)
- Engine: 310/310 tests pass
- Staffing: 37/37 tests pass

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-27 13:18:09 +01:00

166 lines
6.0 KiB
TypeScript

import { z } from "zod";
import { createTRPCRouter, protectedProcedure, controllerProcedure } from "../trpc.js";
import {
getDashboardChargeabilityOverview,
getDashboardDemand,
getDashboardOverview,
getDashboardPeakTimes,
getDashboardTopValueResources,
getDashboardBudgetForecast,
getDashboardSkillGaps,
getDashboardProjectHealth,
} from "@capakraken/application";
import { anonymizeResources, getAnonymizationDirectory } from "../lib/anonymization.js";
import { cacheGet, cacheSet } from "../lib/cache.js";
const DEFAULT_TTL = 60; // seconds
export const dashboardRouter = createTRPCRouter({
getOverview: protectedProcedure.query(async ({ ctx }) => {
const cacheKey = "overview";
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardOverview>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardOverview(ctx.db);
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getPeakTimes: protectedProcedure
.input(
z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
granularity: z.enum(["week", "month"]).default("month"),
groupBy: z.enum(["project", "chapter", "resource"]).default("project"),
}),
)
.query(async ({ ctx, input }) => {
const cacheKey = `peakTimes:${input.startDate}:${input.endDate}:${input.granularity}:${input.groupBy}`;
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardPeakTimes>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardPeakTimes(ctx.db, {
startDate: new Date(input.startDate),
endDate: new Date(input.endDate),
granularity: input.granularity,
groupBy: input.groupBy,
});
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getTopValueResources: protectedProcedure
.input(z.object({ limit: z.number().int().min(1).max(50).default(10) }))
.query(async ({ ctx, input }) => {
const userRole =
(ctx.session.user as { role?: string } | undefined)?.role ?? "USER";
const cacheKey = `topValue:${input.limit}:${userRole}`;
const cached = await cacheGet<Awaited<ReturnType<typeof anonymizeResources>>>(cacheKey);
if (cached) return cached;
const [resources, directory] = await Promise.all([
getDashboardTopValueResources(ctx.db, {
limit: input.limit,
userRole,
}),
getAnonymizationDirectory(ctx.db),
]);
const result = anonymizeResources(resources, directory);
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getDemand: protectedProcedure
.input(
z.object({
startDate: z.string().datetime(),
endDate: z.string().datetime(),
groupBy: z.enum(["project", "person", "chapter"]).default("project"),
}),
)
.query(async ({ ctx, input }) => {
const cacheKey = `demand:${input.startDate}:${input.endDate}:${input.groupBy}`;
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardDemand>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardDemand(ctx.db, {
startDate: new Date(input.startDate),
endDate: new Date(input.endDate),
groupBy: input.groupBy,
});
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getChargeabilityOverview: controllerProcedure
.input(
z.object({
includeProposed: z.boolean().default(false),
topN: z.number().int().min(1).max(50).default(10),
watchlistThreshold: z.number().default(15),
countryIds: z.array(z.string()).optional(),
departed: z.boolean().optional(),
}),
)
.query(async ({ ctx, input }) => {
const cacheKey = `chargeability:${input.includeProposed}:${input.topN}:${input.watchlistThreshold}:${(input.countryIds ?? []).join(",")}:${input.departed ?? ""}`;
type ChargeResult = Awaited<ReturnType<typeof getDashboardChargeabilityOverview>>;
const cached = await cacheGet<{
top: unknown[];
watchlist: unknown[];
[key: string]: unknown;
}>(cacheKey);
if (cached) return cached;
const [overview, directory] = await Promise.all([
getDashboardChargeabilityOverview(ctx.db, {
includeProposed: input.includeProposed,
topN: input.topN,
watchlistThreshold: input.watchlistThreshold,
...(input.countryIds !== undefined ? { countryIds: input.countryIds } : {}),
...(input.departed !== undefined ? { departed: input.departed } : {}),
}),
getAnonymizationDirectory(ctx.db),
]);
const result = {
...overview,
top: anonymizeResources(overview.top, directory),
watchlist: anonymizeResources(overview.watchlist, directory),
};
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getBudgetForecast: protectedProcedure.query(async ({ ctx }) => {
const cacheKey = "budgetForecast";
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardBudgetForecast>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardBudgetForecast(ctx.db);
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getSkillGaps: protectedProcedure.query(async ({ ctx }) => {
const cacheKey = "skillGaps";
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardSkillGaps>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardSkillGaps(ctx.db);
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
getProjectHealth: protectedProcedure.query(async ({ ctx }) => {
const cacheKey = "projectHealth";
const cached = await cacheGet<Awaited<ReturnType<typeof getDashboardProjectHealth>>>(cacheKey);
if (cached) return cached;
const result = await getDashboardProjectHealth(ctx.db);
await cacheSet(cacheKey, result, DEFAULT_TTL);
return result;
}),
});