refactor(api): extract assistant audit-history slice
This commit is contained in:
@@ -41,12 +41,13 @@
|
|||||||
- the blueprint and rate-card read helpers now live in their own domain module, keeping reference-data and pricing lookups out of the monolithic assistant router without changing the assistant contract
|
- the blueprint and rate-card read helpers now live in their own domain module, keeping reference-data and pricing lookups out of the monolithic assistant router without changing the assistant contract
|
||||||
- the dashboard detail, insight summary, anomaly detection, and dynamic report read helpers now live in their own domain module, keeping controller-side analytics reads out of the monolithic assistant router without changing the assistant contract
|
- the dashboard detail, insight summary, anomaly detection, and dynamic report read helpers now live in their own domain module, keeping controller-side analytics reads out of the monolithic assistant router without changing the assistant contract
|
||||||
- the comment listing and comment mutation assistant helpers now live in their own domain module, keeping collaboration-side comment flows out of the monolithic assistant router without changing the assistant contract
|
- the comment listing and comment mutation assistant helpers now live in their own domain module, keeping collaboration-side comment flows out of the monolithic assistant router without changing the assistant contract
|
||||||
|
- the audit-history assistant helpers now live in their own domain module, keeping controller-side change-history reads out of the monolithic assistant router without changing the assistant contract
|
||||||
|
|
||||||
## Next Up
|
## Next Up
|
||||||
|
|
||||||
Pin the next structural cleanup on the API side:
|
Pin the next structural cleanup on the API side:
|
||||||
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
continue splitting `packages/api/src/router/assistant-tools.ts` into domain-oriented tool modules without changing the public tool contract.
|
||||||
The next clean slice should stay adjacent to the extracted domains and target one cohesive analytics or audit block such as `query_change_history` / `get_entity_timeline` or the remaining scenario/narrative/rate-analysis helpers still living in the monolithic router.
|
The next clean slice should stay adjacent to the extracted domains and target one cohesive analytics block such as the remaining scenario/narrative/rate-analysis helpers still living in the monolithic router.
|
||||||
|
|
||||||
## Remaining Major Themes
|
## Remaining Major Themes
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ import {
|
|||||||
commentReadToolDefinitions,
|
commentReadToolDefinitions,
|
||||||
createCommentExecutors,
|
createCommentExecutors,
|
||||||
} from "./assistant-tools/comments.js";
|
} from "./assistant-tools/comments.js";
|
||||||
|
import {
|
||||||
|
auditHistoryToolDefinitions,
|
||||||
|
createAuditHistoryExecutors,
|
||||||
|
} from "./assistant-tools/audit-history.js";
|
||||||
import {
|
import {
|
||||||
withToolAccess,
|
withToolAccess,
|
||||||
type ToolAccessRequirements,
|
type ToolAccessRequirements,
|
||||||
@@ -425,8 +429,6 @@ const LEGACY_MONOLITHIC_TOOL_ACCESS: Partial<Record<string, ToolAccessRequiremen
|
|||||||
lookup_rate: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
lookup_rate: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
simulate_scenario: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
simulate_scenario: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
generate_project_narrative: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
generate_project_narrative: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
query_change_history: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
get_entity_timeline: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
|
||||||
export_resources_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
export_resources_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
export_projects_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
export_projects_csv: { allowedSystemRoles: [...CONTROLLER_ASSISTANT_ROLES] },
|
||||||
import_csv_data: {
|
import_csv_data: {
|
||||||
@@ -2387,40 +2389,7 @@ export const TOOL_DEFINITIONS: ToolDef[] = withToolAccess([
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
...commentMutationToolDefinitions,
|
...commentMutationToolDefinitions,
|
||||||
{
|
...auditHistoryToolDefinitions,
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "query_change_history",
|
|
||||||
description: "Search the audit history for changes to projects, resources, allocations, vacations, or any entity. Reuses the real audit log list API. Controller/manager/admin roles only.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityType: { type: "string", description: "Filter by entity type (e.g. 'Project', 'Resource', 'Allocation', 'Vacation', 'Role', 'Estimate')" },
|
|
||||||
search: { type: "string", description: "Search in entity name or summary text" },
|
|
||||||
userId: { type: "string", description: "Filter by user ID who made the change" },
|
|
||||||
daysBack: { type: "integer", description: "How many days back to search. Default: 7" },
|
|
||||||
action: { type: "string", description: "Filter by action type: CREATE, UPDATE, DELETE, SHIFT, IMPORT" },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 20" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "get_entity_timeline",
|
|
||||||
description: "Get the audit history for a specific entity (project, resource, etc.) via the real audit API. Controller/manager/admin roles only.",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityType: { type: "string", description: "Entity type (e.g. 'Project', 'Resource', 'Allocation')" },
|
|
||||||
entityId: { type: "string", description: "Entity ID" },
|
|
||||||
limit: { type: "integer", description: "Max results. Default: 50" },
|
|
||||||
},
|
|
||||||
required: ["entityType", "entityId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "function",
|
type: "function",
|
||||||
function: {
|
function: {
|
||||||
@@ -2824,6 +2793,10 @@ const executors = {
|
|||||||
toAssistantCommentCreationError,
|
toAssistantCommentCreationError,
|
||||||
toAssistantCommentResolveError,
|
toAssistantCommentResolveError,
|
||||||
}),
|
}),
|
||||||
|
...createAuditHistoryExecutors({
|
||||||
|
createAuditLogCaller,
|
||||||
|
createScopedCallerContext,
|
||||||
|
}),
|
||||||
|
|
||||||
async search_estimates(params: {
|
async search_estimates(params: {
|
||||||
projectCode?: string; query?: string; status?: string; limit?: number;
|
projectCode?: string; query?: string; status?: string; limit?: number;
|
||||||
@@ -3364,57 +3337,6 @@ const executors = {
|
|||||||
return caller.generateProjectNarrative({ projectId: params.projectId });
|
return caller.generateProjectNarrative({ projectId: params.projectId });
|
||||||
},
|
},
|
||||||
|
|
||||||
async query_change_history(params: {
|
|
||||||
entityType?: string;
|
|
||||||
search?: string;
|
|
||||||
userId?: string;
|
|
||||||
daysBack?: number;
|
|
||||||
action?: string;
|
|
||||||
limit?: number;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const limit = Math.min(params.limit ?? 20, 50);
|
|
||||||
const daysBack = params.daysBack ?? 7;
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setDate(startDate.getDate() - daysBack);
|
|
||||||
|
|
||||||
const caller = createAuditLogCaller(createScopedCallerContext(ctx));
|
|
||||||
const result = await caller.listDetail({
|
|
||||||
...(params.entityType ? { entityType: params.entityType } : {}),
|
|
||||||
...(params.userId ? { userId: params.userId } : {}),
|
|
||||||
...(params.action ? { action: params.action } : {}),
|
|
||||||
...(params.search ? { search: params.search } : {}),
|
|
||||||
startDate,
|
|
||||||
limit,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
filters: {
|
|
||||||
entityType: params.entityType ?? null,
|
|
||||||
userId: params.userId ?? null,
|
|
||||||
action: params.action ?? null,
|
|
||||||
search: params.search ?? null,
|
|
||||||
daysBack,
|
|
||||||
},
|
|
||||||
itemCount: result.items.length,
|
|
||||||
nextCursor: result.nextCursor ?? null,
|
|
||||||
items: result.items,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async get_entity_timeline(params: {
|
|
||||||
entityType: string;
|
|
||||||
entityId: string;
|
|
||||||
limit?: number;
|
|
||||||
}, ctx: ToolContext) {
|
|
||||||
const limit = Math.min(params.limit ?? 50, 200);
|
|
||||||
const caller = createAuditLogCaller(createScopedCallerContext(ctx));
|
|
||||||
return caller.getByEntityDetail({
|
|
||||||
entityType: params.entityType,
|
|
||||||
entityId: params.entityId,
|
|
||||||
limit,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async export_resources_csv(_params: Record<string, never>, ctx: ToolContext) {
|
async export_resources_csv(_params: Record<string, never>, ctx: ToolContext) {
|
||||||
const caller = createImportExportCaller(createScopedCallerContext(ctx));
|
const caller = createImportExportCaller(createScopedCallerContext(ctx));
|
||||||
const csv = await caller.exportResourcesCSV();
|
const csv = await caller.exportResourcesCSV();
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
import { SystemRole } from "@capakraken/shared";
|
||||||
|
import type { TRPCContext } from "../../trpc.js";
|
||||||
|
import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js";
|
||||||
|
|
||||||
|
type AuditListItem = {
|
||||||
|
id: string;
|
||||||
|
entityType: string;
|
||||||
|
entityId: string;
|
||||||
|
entityName: string | null;
|
||||||
|
action: string;
|
||||||
|
userId: string | null;
|
||||||
|
source: string | null;
|
||||||
|
summary: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AuditHistoryDeps = {
|
||||||
|
createAuditLogCaller: (ctx: TRPCContext) => {
|
||||||
|
listDetail: (params: {
|
||||||
|
entityType?: string;
|
||||||
|
entityId?: string;
|
||||||
|
userId?: string;
|
||||||
|
action?: string;
|
||||||
|
source?: string;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
search?: string;
|
||||||
|
limit: number;
|
||||||
|
cursor?: string;
|
||||||
|
}) => Promise<{
|
||||||
|
items: AuditListItem[];
|
||||||
|
nextCursor: string | null;
|
||||||
|
}>;
|
||||||
|
getByEntityDetail: (params: {
|
||||||
|
entityType: string;
|
||||||
|
entityId: string;
|
||||||
|
limit: number;
|
||||||
|
}) => Promise<unknown>;
|
||||||
|
};
|
||||||
|
createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const auditHistoryToolDefinitions: ToolDef[] = withToolAccess([
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "query_change_history",
|
||||||
|
description: "Search the audit history for changes to projects, resources, allocations, vacations, or any entity. Reuses the real audit log list API. Controller/manager/admin roles only.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityType: { type: "string", description: "Filter by entity type (e.g. 'Project', 'Resource', 'Allocation', 'Vacation', 'Role', 'Estimate')" },
|
||||||
|
search: { type: "string", description: "Search in entity name or summary text" },
|
||||||
|
userId: { type: "string", description: "Filter by user ID who made the change" },
|
||||||
|
daysBack: { type: "integer", description: "How many days back to search. Default: 7" },
|
||||||
|
action: { type: "string", description: "Filter by action type: CREATE, UPDATE, DELETE, SHIFT, IMPORT" },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 20" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
function: {
|
||||||
|
name: "get_entity_timeline",
|
||||||
|
description: "Get the audit history for a specific entity (project, resource, etc.) via the real audit API. Controller/manager/admin roles only.",
|
||||||
|
parameters: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
entityType: { type: "string", description: "Entity type (e.g. 'Project', 'Resource', 'Allocation')" },
|
||||||
|
entityId: { type: "string", description: "Entity ID" },
|
||||||
|
limit: { type: "integer", description: "Max results. Default: 50" },
|
||||||
|
},
|
||||||
|
required: ["entityType", "entityId"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
query_change_history: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
get_entity_timeline: {
|
||||||
|
allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER, SystemRole.CONTROLLER],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function createAuditHistoryExecutors(
|
||||||
|
deps: AuditHistoryDeps,
|
||||||
|
): Record<string, ToolExecutor> {
|
||||||
|
return {
|
||||||
|
async query_change_history(
|
||||||
|
params: {
|
||||||
|
entityType?: string;
|
||||||
|
search?: string;
|
||||||
|
userId?: string;
|
||||||
|
daysBack?: number;
|
||||||
|
action?: string;
|
||||||
|
limit?: number;
|
||||||
|
},
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = Math.min(params.limit ?? 20, 50);
|
||||||
|
const daysBack = params.daysBack ?? 7;
|
||||||
|
const startDate = new Date();
|
||||||
|
startDate.setDate(startDate.getDate() - daysBack);
|
||||||
|
|
||||||
|
const caller = deps.createAuditLogCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
const result = await caller.listDetail({
|
||||||
|
...(params.entityType ? { entityType: params.entityType } : {}),
|
||||||
|
...(params.userId ? { userId: params.userId } : {}),
|
||||||
|
...(params.action ? { action: params.action } : {}),
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
startDate,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
entityType: params.entityType ?? null,
|
||||||
|
userId: params.userId ?? null,
|
||||||
|
action: params.action ?? null,
|
||||||
|
search: params.search ?? null,
|
||||||
|
daysBack,
|
||||||
|
},
|
||||||
|
itemCount: result.items.length,
|
||||||
|
nextCursor: result.nextCursor ?? null,
|
||||||
|
items: result.items,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async get_entity_timeline(
|
||||||
|
params: { entityType: string; entityId: string; limit?: number },
|
||||||
|
ctx: ToolContext,
|
||||||
|
) {
|
||||||
|
const limit = Math.min(params.limit ?? 50, 200);
|
||||||
|
const caller = deps.createAuditLogCaller(deps.createScopedCallerContext(ctx));
|
||||||
|
return caller.getByEntityDetail({
|
||||||
|
entityType: params.entityType,
|
||||||
|
entityId: params.entityId,
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user