mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-15 02:20:38 +09:00
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:
parent
948e8e8c94
commit
c9d7cbfe44
24 changed files with 624 additions and 0 deletions
42
server/src/services/activity.ts
Normal file
42
server/src/services/activity.ts
Normal 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]),
|
||||
};
|
||||
}
|
||||
38
server/src/services/agents.ts
Normal file
38
server/src/services/agents.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
38
server/src/services/goals.ts
Normal file
38
server/src/services/goals.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
5
server/src/services/index.ts
Normal file
5
server/src/services/index.ts
Normal 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";
|
||||
38
server/src/services/issues.ts
Normal file
38
server/src/services/issues.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
38
server/src/services/projects.ts
Normal file
38
server/src/services/projects.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue