Add API server with routes, services, and middleware

Express server with CRUD routes for agents, goals, issues, projects,
and activity log. Includes validation middleware, structured error
handling, request logging, and health check endpoint with tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-16 13:31:58 -06:00
parent 948e8e8c94
commit c9d7cbfe44
24 changed files with 624 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
import { eq, and, desc } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { activityLog } from "@paperclip/db";
export interface ActivityFilters {
agentId?: string;
entityType?: string;
entityId?: string;
}
export function activityService(db: Db) {
return {
list: (filters?: ActivityFilters) => {
const conditions = [];
if (filters?.agentId) {
conditions.push(eq(activityLog.agentId, filters.agentId));
}
if (filters?.entityType) {
conditions.push(eq(activityLog.entityType, filters.entityType));
}
if (filters?.entityId) {
conditions.push(eq(activityLog.entityId, filters.entityId));
}
const query = db.select().from(activityLog);
if (conditions.length > 0) {
return query.where(and(...conditions)).orderBy(desc(activityLog.createdAt));
}
return query.orderBy(desc(activityLog.createdAt));
},
create: (data: typeof activityLog.$inferInsert) =>
db
.insert(activityLog)
.values(data)
.returning()
.then((rows) => rows[0]),
};
}

View File

@@ -0,0 +1,38 @@
import { eq } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { agents } from "@paperclip/db";
export function agentService(db: Db) {
return {
list: () => db.select().from(agents),
getById: (id: string) =>
db
.select()
.from(agents)
.where(eq(agents.id, id))
.then((rows) => rows[0] ?? null),
create: (data: typeof agents.$inferInsert) =>
db
.insert(agents)
.values(data)
.returning()
.then((rows) => rows[0]),
update: (id: string, data: Partial<typeof agents.$inferInsert>) =>
db
.update(agents)
.set({ ...data, updatedAt: new Date() })
.where(eq(agents.id, id))
.returning()
.then((rows) => rows[0] ?? null),
remove: (id: string) =>
db
.delete(agents)
.where(eq(agents.id, id))
.returning()
.then((rows) => rows[0] ?? null),
};
}

View File

@@ -0,0 +1,38 @@
import { eq } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { goals } from "@paperclip/db";
export function goalService(db: Db) {
return {
list: () => db.select().from(goals),
getById: (id: string) =>
db
.select()
.from(goals)
.where(eq(goals.id, id))
.then((rows) => rows[0] ?? null),
create: (data: typeof goals.$inferInsert) =>
db
.insert(goals)
.values(data)
.returning()
.then((rows) => rows[0]),
update: (id: string, data: Partial<typeof goals.$inferInsert>) =>
db
.update(goals)
.set({ ...data, updatedAt: new Date() })
.where(eq(goals.id, id))
.returning()
.then((rows) => rows[0] ?? null),
remove: (id: string) =>
db
.delete(goals)
.where(eq(goals.id, id))
.returning()
.then((rows) => rows[0] ?? null),
};
}

View File

@@ -0,0 +1,5 @@
export { agentService } from "./agents.js";
export { projectService } from "./projects.js";
export { issueService } from "./issues.js";
export { goalService } from "./goals.js";
export { activityService, type ActivityFilters } from "./activity.js";

View File

@@ -0,0 +1,38 @@
import { eq } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { issues } from "@paperclip/db";
export function issueService(db: Db) {
return {
list: () => db.select().from(issues),
getById: (id: string) =>
db
.select()
.from(issues)
.where(eq(issues.id, id))
.then((rows) => rows[0] ?? null),
create: (data: typeof issues.$inferInsert) =>
db
.insert(issues)
.values(data)
.returning()
.then((rows) => rows[0]),
update: (id: string, data: Partial<typeof issues.$inferInsert>) =>
db
.update(issues)
.set({ ...data, updatedAt: new Date() })
.where(eq(issues.id, id))
.returning()
.then((rows) => rows[0] ?? null),
remove: (id: string) =>
db
.delete(issues)
.where(eq(issues.id, id))
.returning()
.then((rows) => rows[0] ?? null),
};
}

View File

@@ -0,0 +1,38 @@
import { eq } from "drizzle-orm";
import type { Db } from "@paperclip/db";
import { projects } from "@paperclip/db";
export function projectService(db: Db) {
return {
list: () => db.select().from(projects),
getById: (id: string) =>
db
.select()
.from(projects)
.where(eq(projects.id, id))
.then((rows) => rows[0] ?? null),
create: (data: typeof projects.$inferInsert) =>
db
.insert(projects)
.values(data)
.returning()
.then((rows) => rows[0]),
update: (id: string, data: Partial<typeof projects.$inferInsert>) =>
db
.update(projects)
.set({ ...data, updatedAt: new Date() })
.where(eq(projects.id, id))
.returning()
.then((rows) => rows[0] ?? null),
remove: (id: string) =>
db
.delete(projects)
.where(eq(projects.id, id))
.returning()
.then((rows) => rows[0] ?? null),
};
}