added repo
This commit is contained in:
223
src/index.ts
Normal file
223
src/index.ts
Normal file
@@ -0,0 +1,223 @@
|
||||
import type { Database } from "bun:sqlite";
|
||||
import { getConfig } from "./config";
|
||||
import { ensureSeededDatabase } from "./database";
|
||||
import { openApiDocument } from "./openapi";
|
||||
import {
|
||||
createTask,
|
||||
getOrganizationSummary,
|
||||
getTaskDetail,
|
||||
listOrganizations,
|
||||
listProjects,
|
||||
listTasks,
|
||||
listUsers,
|
||||
updateTask,
|
||||
} from "./repository";
|
||||
|
||||
function json(body: unknown, init: ResponseInit = {}) {
|
||||
const headers = new Headers(init.headers);
|
||||
headers.set("content-type", "application/json; charset=utf-8");
|
||||
headers.set("access-control-allow-origin", "*");
|
||||
headers.set("access-control-allow-methods", "GET,POST,PATCH,OPTIONS");
|
||||
headers.set("access-control-allow-headers", "content-type");
|
||||
return new Response(JSON.stringify(body, null, 2), { ...init, headers });
|
||||
}
|
||||
|
||||
function notFound(message: string) {
|
||||
return json({ error: message }, { status: 404 });
|
||||
}
|
||||
|
||||
function badRequest(message: string) {
|
||||
return json({ error: message }, { status: 400 });
|
||||
}
|
||||
|
||||
async function readJson(request: Request) {
|
||||
try {
|
||||
return await request.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function validateCreateTask(body: unknown) {
|
||||
if (!isRecord(body)) {
|
||||
return { error: "Request body must be a JSON object." };
|
||||
}
|
||||
|
||||
if (typeof body.orgId !== "string" || body.orgId.trim() === "") {
|
||||
return { error: "orgId is required." };
|
||||
}
|
||||
if (typeof body.projectId !== "string" || body.projectId.trim() === "") {
|
||||
return { error: "projectId is required." };
|
||||
}
|
||||
if (typeof body.title !== "string" || body.title.trim() === "") {
|
||||
return { error: "title is required." };
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
orgId: body.orgId.trim(),
|
||||
projectId: body.projectId.trim(),
|
||||
assigneeUserId:
|
||||
typeof body.assigneeUserId === "string" ? body.assigneeUserId.trim() : null,
|
||||
title: body.title.trim(),
|
||||
description:
|
||||
typeof body.description === "string" ? body.description.trim() : undefined,
|
||||
status: typeof body.status === "string" ? body.status.trim() : undefined,
|
||||
priority: typeof body.priority === "string" ? body.priority.trim() : undefined,
|
||||
storyPoints:
|
||||
typeof body.storyPoints === "number" ? Math.trunc(body.storyPoints) : undefined,
|
||||
dueDate: typeof body.dueDate === "string" ? body.dueDate : null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function validateUpdateTask(body: unknown) {
|
||||
if (!isRecord(body)) {
|
||||
return { error: "Request body must be a JSON object." };
|
||||
}
|
||||
|
||||
return {
|
||||
value: {
|
||||
title: typeof body.title === "string" ? body.title.trim() : undefined,
|
||||
description:
|
||||
typeof body.description === "string" ? body.description.trim() : undefined,
|
||||
status: typeof body.status === "string" ? body.status.trim() : undefined,
|
||||
priority: typeof body.priority === "string" ? body.priority.trim() : undefined,
|
||||
assigneeUserId:
|
||||
typeof body.assigneeUserId === "string"
|
||||
? body.assigneeUserId.trim()
|
||||
: body.assigneeUserId === null
|
||||
? null
|
||||
: undefined,
|
||||
storyPoints:
|
||||
typeof body.storyPoints === "number"
|
||||
? Math.trunc(body.storyPoints)
|
||||
: body.storyPoints === null
|
||||
? null
|
||||
: undefined,
|
||||
dueDate:
|
||||
typeof body.dueDate === "string"
|
||||
? body.dueDate
|
||||
: body.dueDate === null
|
||||
? null
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createServer(db: Database) {
|
||||
return {
|
||||
port: getConfig().port,
|
||||
fetch: async (request: Request) => {
|
||||
if (request.method === "OPTIONS") {
|
||||
return json({ ok: true });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const parts = url.pathname.split("/").filter(Boolean);
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/health") {
|
||||
return json({ status: "ok" });
|
||||
}
|
||||
|
||||
if (
|
||||
request.method === "GET" &&
|
||||
(url.pathname === "/openapi.json" ||
|
||||
url.pathname === "/swagger.json" ||
|
||||
url.pathname === "/api-docs")
|
||||
) {
|
||||
return json(openApiDocument);
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/orgs") {
|
||||
return json({ organizations: listOrganizations(db) });
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/users") {
|
||||
return json({ users: listUsers(db, url.searchParams.get("orgId")) });
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/projects") {
|
||||
return json({ projects: listProjects(db, url.searchParams.get("orgId")) });
|
||||
}
|
||||
|
||||
if (request.method === "GET" && url.pathname === "/tasks") {
|
||||
return json({
|
||||
tasks: listTasks(db, {
|
||||
orgId: url.searchParams.get("orgId"),
|
||||
projectId: url.searchParams.get("projectId"),
|
||||
status: url.searchParams.get("status"),
|
||||
priority: url.searchParams.get("priority"),
|
||||
assigneeUserId: url.searchParams.get("assigneeUserId"),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (request.method === "POST" && url.pathname === "/tasks") {
|
||||
const payload = validateCreateTask(await readJson(request));
|
||||
if ("error" in payload) {
|
||||
return badRequest(payload.error);
|
||||
}
|
||||
|
||||
const task = createTask(db, payload.value);
|
||||
return json({ task }, { status: 201 });
|
||||
}
|
||||
|
||||
if (parts[0] === "orgs" && parts.length === 2 && request.method === "GET") {
|
||||
const summary = getOrganizationSummary(db, parts[1]!);
|
||||
return summary ? json({ organization: summary }) : notFound("Organization not found.");
|
||||
}
|
||||
|
||||
if (parts[0] === "orgs" && parts[2] === "projects" && request.method === "GET") {
|
||||
return json({ projects: listProjects(db, parts[1]!) });
|
||||
}
|
||||
|
||||
if (parts[0] === "orgs" && parts[2] === "tasks" && request.method === "GET") {
|
||||
return json({
|
||||
tasks: listTasks(db, {
|
||||
orgId: parts[1]!,
|
||||
projectId: url.searchParams.get("projectId"),
|
||||
status: url.searchParams.get("status"),
|
||||
priority: url.searchParams.get("priority"),
|
||||
assigneeUserId: url.searchParams.get("assigneeUserId"),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (parts[0] === "tasks" && parts.length === 2 && request.method === "GET") {
|
||||
const task = getTaskDetail(db, parts[1]!);
|
||||
return task ? json({ task }) : notFound("Task not found.");
|
||||
}
|
||||
|
||||
if (parts[0] === "tasks" && parts.length === 2 && request.method === "PATCH") {
|
||||
const payload = validateUpdateTask(await readJson(request));
|
||||
if ("error" in payload) {
|
||||
return badRequest(payload.error);
|
||||
}
|
||||
|
||||
const task = updateTask(db, parts[1]!, payload.value);
|
||||
return task ? json({ task }) : notFound("Task not found.");
|
||||
}
|
||||
|
||||
return notFound("Route not found.");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function main() {
|
||||
const config = getConfig();
|
||||
const db = ensureSeededDatabase(config.dbPath);
|
||||
Bun.serve(createServer(db));
|
||||
console.info("mock-task-api:listening", {
|
||||
port: config.port,
|
||||
dbPath: config.dbPath,
|
||||
});
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
main();
|
||||
}
|
||||
Reference in New Issue
Block a user