2026-02-19 09:09:40 -06:00
|
|
|
import { and, desc, eq, isNotNull, sql } from "drizzle-orm";
|
2026-02-16 13:31:58 -06:00
|
|
|
import type { Db } from "@paperclip/db";
|
2026-02-19 09:09:40 -06:00
|
|
|
import { activityLog, heartbeatRuns, issues } from "@paperclip/db";
|
2026-02-16 13:31:58 -06:00
|
|
|
|
|
|
|
|
export interface ActivityFilters {
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
companyId: string;
|
2026-02-16 13:31:58 -06:00
|
|
|
agentId?: string;
|
|
|
|
|
entityType?: string;
|
|
|
|
|
entityId?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function activityService(db: Db) {
|
2026-02-19 09:09:40 -06:00
|
|
|
const issueIdAsText = sql<string>`${issues.id}::text`;
|
2026-02-16 13:31:58 -06:00
|
|
|
return {
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
list: (filters: ActivityFilters) => {
|
|
|
|
|
const conditions = [eq(activityLog.companyId, filters.companyId)];
|
2026-02-16 13:31:58 -06:00
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (filters.agentId) {
|
2026-02-16 13:31:58 -06:00
|
|
|
conditions.push(eq(activityLog.agentId, filters.agentId));
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (filters.entityType) {
|
2026-02-16 13:31:58 -06:00
|
|
|
conditions.push(eq(activityLog.entityType, filters.entityType));
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (filters.entityId) {
|
2026-02-16 13:31:58 -06:00
|
|
|
conditions.push(eq(activityLog.entityId, filters.entityId));
|
|
|
|
|
}
|
|
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
return db.select().from(activityLog).where(and(...conditions)).orderBy(desc(activityLog.createdAt));
|
2026-02-16 13:31:58 -06:00
|
|
|
},
|
|
|
|
|
|
2026-02-19 09:09:40 -06:00
|
|
|
forIssue: (issueId: string) =>
|
|
|
|
|
db
|
|
|
|
|
.select()
|
|
|
|
|
.from(activityLog)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(activityLog.entityType, "issue"),
|
|
|
|
|
eq(activityLog.entityId, issueId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.orderBy(desc(activityLog.createdAt)),
|
|
|
|
|
|
|
|
|
|
runsForIssue: (issueId: string) =>
|
|
|
|
|
db
|
|
|
|
|
.selectDistinctOn([activityLog.runId], {
|
|
|
|
|
runId: activityLog.runId,
|
|
|
|
|
status: heartbeatRuns.status,
|
|
|
|
|
agentId: heartbeatRuns.agentId,
|
|
|
|
|
startedAt: heartbeatRuns.startedAt,
|
|
|
|
|
finishedAt: heartbeatRuns.finishedAt,
|
|
|
|
|
createdAt: heartbeatRuns.createdAt,
|
|
|
|
|
invocationSource: heartbeatRuns.invocationSource,
|
|
|
|
|
})
|
|
|
|
|
.from(activityLog)
|
|
|
|
|
.innerJoin(heartbeatRuns, eq(activityLog.runId, heartbeatRuns.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(activityLog.entityType, "issue"),
|
|
|
|
|
eq(activityLog.entityId, issueId),
|
|
|
|
|
isNotNull(activityLog.runId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.orderBy(activityLog.runId, desc(heartbeatRuns.createdAt)),
|
|
|
|
|
|
|
|
|
|
issuesForRun: (runId: string) =>
|
|
|
|
|
db
|
|
|
|
|
.selectDistinctOn([issueIdAsText], {
|
|
|
|
|
issueId: issues.id,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
})
|
|
|
|
|
.from(activityLog)
|
|
|
|
|
.innerJoin(issues, eq(activityLog.entityId, issueIdAsText))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(activityLog.runId, runId),
|
|
|
|
|
eq(activityLog.entityType, "issue"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.orderBy(issueIdAsText),
|
|
|
|
|
|
2026-02-16 13:31:58 -06:00
|
|
|
create: (data: typeof activityLog.$inferInsert) =>
|
|
|
|
|
db
|
|
|
|
|
.insert(activityLog)
|
|
|
|
|
.values(data)
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0]),
|
|
|
|
|
};
|
|
|
|
|
}
|