refactor(api-server): move work-graph SQL into db domain methods

Replace six inline queryAll calls in routes-work-graph.ts with domain
methods. db.entityCrud gains listGoals, getGoalForUpdate, listProjects,
getProjectForUpdate. db.knowledge gains listSprints. db.company gains
getDashboardSnapshot, which returns the raw agent/task/activity rows
the dashboard handler aggregates. The route-level row shapes are
preserved in the new GoalListRow / ProjectListRow / SprintListRow
types so HTTP responses remain identical.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-16 22:34:46 -04:00
1 parent 2be1bd1612
commit efb9fdb8cc
6 files changed
+162 -60

No files matched your search

+38
View File
@@ -21,6 +21,44 @@ export function buildCompanySettingsMethods(
},
) {
return {
/**
* Returns the raw inputs for the GET /api/companies/:id/dashboard
* endpoint: agent statuses, task statuses, and the last 20 activity rows.
* Route code aggregates counts by status on top of this raw data.
*/
getDashboardSnapshot(companyId: string): {
agents: Array<{ id: string; status: string }>;
tasks: Array<{ id: string; status: string }>;
recentActivity: Array<{
id: string;
companyId: string;
actor: string;
action: string;
entityType: string;
entityId: string;
detail: string | null;
createdAt: string;
}>;
} {
return {
agents: readAll<{ id: string; status: string }>(
"select id, status from agents where company_id = ?",
companyId,
),
tasks: readAll<{ id: string; status: string }>(
"select id, status from tasks where company_id = ?",
companyId,
),
recentActivity: readAll<{
id: string; companyId: string; actor: string; action: string;
entityType: string; entityId: string; detail: string | null; createdAt: string;
}>(
"select id, company_id as companyId, actor, action, entity_type as entityType, entity_id as entityId, detail, created_at as createdAt from activity where company_id = ? order by created_at desc limit 20",
companyId,
),
};
},
getCompanyMetrics(companyId: string, days: number = 30): CompanyMetrics {
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
const db = getDb();
+81
View File
@@ -3,6 +3,40 @@ import type { AgentRecord, ConnectorId, TaskRecord } from "@shared/types";
import type { ReadAll, Transaction } from "./helpers";
import { ensureId, normalizeOptionalId, nowIso } from "./helpers";
/**
* Goal row shape returned by listGoals / getGoalForUpdate. Mirrors the
* historical inline SQL projection in routes-work-graph.ts so HTTP
* responses stay byte-stable.
*/
export type GoalListRow = {
id: string;
companyId: string;
title: string;
description: string;
parentId: string | null;
ownerAgentId: string | null;
status: string;
createdAt: string;
updatedAt: string;
};
/**
* Project row shape returned by listProjects / getProjectForUpdate.
* Mirrors the historical inline SQL projection in routes-work-graph.ts.
*/
export type ProjectListRow = {
id: string;
companyId: string;
goalId: string | null;
name: string;
description: string;
leadAgentId: string | null;
status: string;
targetDate: string | null;
createdAt: string;
updatedAt: string;
};
export function buildEntityCrudMethods(
getDb: () => DatabaseSync,
readAll: ReadAll,
@@ -37,6 +71,53 @@ export function buildEntityCrudMethods(
return row;
},
/**
* Returns all goals for a company. Used by GET /api/companies/:id/goals.
*/
listGoals(companyId: string): GoalListRow[] {
return readAll<GoalListRow>(
"select id, company_id as companyId, title, description, parent_id as parentId, owner_agent_id as ownerAgentId, status, created_at as createdAt, updated_at as updatedAt from goals where company_id = ? order by created_at asc",
companyId,
);
},
/**
* Returns the goal lookup row used by PATCH /api/goals/:goalId.
* Returns null when the goal is missing or belongs to another company.
*/
getGoalForUpdate(goalId: string, companyId: string): GoalListRow | null {
const row = getDb()
.prepare(
"select id, company_id as companyId, title, description, parent_id as parentId, owner_agent_id as ownerAgentId, status, created_at as createdAt, updated_at as updatedAt from goals where id = ? and company_id = ? limit 1",
)
.get(goalId, companyId) as GoalListRow | undefined;
return row ?? null;
},
/**
* Returns all projects for a company. Used by
* GET /api/companies/:id/projects.
*/
listProjects(companyId: string): ProjectListRow[] {
return readAll<ProjectListRow>(
"select id, company_id as companyId, goal_id as goalId, name, description, lead_agent_id as leadAgentId, status, target_date as targetDate, created_at as createdAt, updated_at as updatedAt from projects where company_id = ? order by created_at desc",
companyId,
);
},
/**
* Returns the project lookup row used by PATCH /api/projects/:projectId.
* Returns null when the project is missing or belongs to another company.
*/
getProjectForUpdate(projectId: string, companyId: string): ProjectListRow | null {
const row = getDb()
.prepare(
"select id, company_id as companyId, goal_id as goalId, name, description, lead_agent_id as leadAgentId, status, target_date as targetDate, created_at as createdAt, updated_at as updatedAt from projects where id = ? and company_id = ? limit 1",
)
.get(projectId, companyId) as ProjectListRow | undefined;
return row ?? null;
},
saveGoal(input: { id?: string | null; companyId: string; title: string; description: string; parentId?: string | null; ownerAgentId?: string | null; status: string }) {
const id = ensureId(input.id ?? null);
const ts = nowIso();
+31
View File
@@ -28,6 +28,26 @@ export type MeetingListRow = {
const MEETING_LIST_COLUMNS =
"select id, company_id as companyId, type, title, organizer_agent_id as organizerAgentId, participant_agent_ids as participantAgentIds, scheduled_at as scheduledAt, duration_minutes as durationMinutes, agenda_json as agendaJson, notes_json as notesJson, decisions_json as decisionsJson, action_items_json as actionItemsJson, status, created_at as createdAt, updated_at as updatedAt from meetings";
/**
* Sprint row shape returned by listSprints — used by
* GET /api/companies/:companyId/sprints. Mirrors the historical inline
* SQL projection.
*/
export type SprintListRow = {
id: string;
companyId: string;
name: string;
goal: string;
startDate: string;
endDate: string;
status: string;
retrospectiveNotes: string | null;
velocityPoints: number;
completedPoints: number;
createdAt: string;
updatedAt: string;
};
/**
* Knowledge entry row shape returned by listKnowledgeEntries — used by
* GET /api/companies/:companyId/knowledge. Mirrors the historical inline
@@ -151,5 +171,16 @@ export function buildKnowledgeMethods(
deleteSprint(id: string, companyId: string) {
getDb().prepare("delete from sprints where id = ? and company_id = ?").run(id, companyId);
},
/**
* Returns all sprints for a company, ordered by start_date desc.
* Used by GET /api/companies/:companyId/sprints.
*/
listSprints(companyId: string): SprintListRow[] {
return readAll<SprintListRow>(
"select id, company_id as companyId, name, goal, start_date as startDate, end_date as endDate, status, retrospective_notes as retrospectiveNotes, velocity_points as velocityPoints, completed_points as completedPoints, created_at as createdAt, updated_at as updatedAt from sprints where company_id = ? order by start_date desc",
companyId,
);
},
};
}