154 lines
4.5 KiB
TypeScript
154 lines
4.5 KiB
TypeScript
import { Elysia, t } from "elysia";
|
|
import { db } from "../db";
|
|
import { projects, tasks } from "../db/schema";
|
|
import { eq, asc, desc } from "drizzle-orm";
|
|
import { auth } from "../lib/auth";
|
|
|
|
const BEARER_TOKEN = process.env.API_BEARER_TOKEN || "hammer-dev-token";
|
|
|
|
async function requireSessionOrBearer(
|
|
request: Request,
|
|
headers: Record<string, string | undefined>
|
|
) {
|
|
const authHeader = headers["authorization"];
|
|
if (authHeader === `Bearer ${BEARER_TOKEN}`) return;
|
|
try {
|
|
const session = await auth.api.getSession({ headers: request.headers });
|
|
if (session) return;
|
|
} catch {}
|
|
throw new Error("Unauthorized");
|
|
}
|
|
|
|
export const projectRoutes = new Elysia({ prefix: "/api/projects" })
|
|
.onError(({ error, set }) => {
|
|
const msg = error?.message || String(error);
|
|
if (msg === "Unauthorized") {
|
|
set.status = 401;
|
|
return { error: "Unauthorized" };
|
|
}
|
|
if (msg === "Project not found") {
|
|
set.status = 404;
|
|
return { error: "Project not found" };
|
|
}
|
|
console.error("Project route error:", msg);
|
|
set.status = 500;
|
|
return { error: "Internal server error" };
|
|
})
|
|
|
|
// GET all projects
|
|
.get("/", async ({ request, headers }) => {
|
|
await requireSessionOrBearer(request, headers);
|
|
const allProjects = await db
|
|
.select()
|
|
.from(projects)
|
|
.orderBy(asc(projects.name));
|
|
return allProjects;
|
|
})
|
|
|
|
// POST create project
|
|
.post(
|
|
"/",
|
|
async ({ body, request, headers }) => {
|
|
await requireSessionOrBearer(request, headers);
|
|
const newProject = await db
|
|
.insert(projects)
|
|
.values({
|
|
name: body.name,
|
|
description: body.description,
|
|
context: body.context,
|
|
repos: body.repos || [],
|
|
links: body.links || [],
|
|
})
|
|
.returning();
|
|
return newProject[0];
|
|
},
|
|
{
|
|
body: t.Object({
|
|
name: t.String(),
|
|
description: t.Optional(t.String()),
|
|
context: t.Optional(t.String()),
|
|
repos: t.Optional(t.Array(t.String())),
|
|
links: t.Optional(
|
|
t.Array(t.Object({ label: t.String(), url: t.String() }))
|
|
),
|
|
}),
|
|
}
|
|
)
|
|
|
|
// GET single project with its tasks
|
|
.get(
|
|
"/:id",
|
|
async ({ params, request, headers }) => {
|
|
await requireSessionOrBearer(request, headers);
|
|
const project = await db
|
|
.select()
|
|
.from(projects)
|
|
.where(eq(projects.id, params.id));
|
|
if (!project.length) throw new Error("Project not found");
|
|
|
|
const projectTasks = await db
|
|
.select()
|
|
.from(tasks)
|
|
.where(eq(tasks.projectId, params.id))
|
|
.orderBy(asc(tasks.position), desc(tasks.createdAt));
|
|
|
|
return { ...project[0], tasks: projectTasks };
|
|
},
|
|
{ params: t.Object({ id: t.String() }) }
|
|
)
|
|
|
|
// PATCH update project
|
|
.patch(
|
|
"/:id",
|
|
async ({ params, body, request, headers }) => {
|
|
await requireSessionOrBearer(request, headers);
|
|
const updates: Record<string, any> = { updatedAt: new Date() };
|
|
if (body.name !== undefined) updates.name = body.name;
|
|
if (body.description !== undefined) updates.description = body.description;
|
|
if (body.context !== undefined) updates.context = body.context;
|
|
if (body.repos !== undefined) updates.repos = body.repos;
|
|
if (body.links !== undefined) updates.links = body.links;
|
|
|
|
const updated = await db
|
|
.update(projects)
|
|
.set(updates)
|
|
.where(eq(projects.id, params.id))
|
|
.returning();
|
|
if (!updated.length) throw new Error("Project not found");
|
|
return updated[0];
|
|
},
|
|
{
|
|
params: t.Object({ id: t.String() }),
|
|
body: t.Object({
|
|
name: t.Optional(t.String()),
|
|
description: t.Optional(t.String()),
|
|
context: t.Optional(t.String()),
|
|
repos: t.Optional(t.Array(t.String())),
|
|
links: t.Optional(
|
|
t.Array(t.Object({ label: t.String(), url: t.String() }))
|
|
),
|
|
}),
|
|
}
|
|
)
|
|
|
|
// DELETE project (unlinks tasks, doesn't delete them)
|
|
.delete(
|
|
"/:id",
|
|
async ({ params, request, headers }) => {
|
|
await requireSessionOrBearer(request, headers);
|
|
// Unlink tasks first
|
|
await db
|
|
.update(tasks)
|
|
.set({ projectId: null, updatedAt: new Date() })
|
|
.where(eq(tasks.projectId, params.id));
|
|
// Delete project
|
|
const deleted = await db
|
|
.delete(projects)
|
|
.where(eq(projects.id, params.id))
|
|
.returning();
|
|
if (!deleted.length) throw new Error("Project not found");
|
|
return { success: true };
|
|
},
|
|
{ params: t.Object({ id: t.String() }) }
|
|
);
|