cd78f72f33
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>
166 lines
6.0 KiB
TypeScript
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;
|
|
}),
|
|
});
|