Files
sankofa-hw-infra/apps/api/src/routes/v1/offers.ts
defiQUG 93df3c8c20
Some checks failed
CI / lint-and-test (push) Has been cancelled
Initial commit: add .gitignore and README
2026-02-09 21:51:50 -08:00

58 lines
3.7 KiB
TypeScript

import type { FastifyInstance } from "fastify";
import { eq, and, sql } from "drizzle-orm";
import { offers as offersTable } from "@sankofa/schema";
export async function offersRoutes(app: FastifyInstance) {
const db = app.db;
const listSchema = { querystring: { type: "object", properties: { limit: { type: "integer" }, offset: { type: "integer" } } } };
app.get("/", { schema: listSchema }, async (req, reply) => {
const orgId = app.orgId(req);
const vid = app.vendorId(req);
const limit = Math.min(Number((req.query as { limit?: number }).limit) || 50, 100);
const offset = Number((req.query as { offset?: number }).offset) || 0;
const conditions = [eq(offersTable.orgId, orgId)] as ReturnType<typeof eq>[];
if (vid) conditions.push(eq(offersTable.vendorId, vid));
const list = await db.select().from(offersTable).where(and(...conditions)).limit(limit).offset(offset);
const [{ total }] = await db.select({ total: sql<number>`count(*)::int` }).from(offersTable).where(and(...conditions));
return reply.send({ data: list, total });
});
app.get<{ Params: { id: string } }>("/:id", async (req, reply) => {
const orgId = app.orgId(req);
const vid = app.vendorId(req);
const conditions = [eq(offersTable.id, req.params.id), eq(offersTable.orgId, orgId)] as ReturnType<typeof eq>[];
if (vid) conditions.push(eq(offersTable.vendorId, vid));
const [row] = await db.select().from(offersTable).where(and(...conditions));
if (!row) return reply.notFound();
return reply.send(row);
});
app.post<{ Body: { vendorId?: string; sku?: string; mpn?: string; quantity: number; unitPrice: string; incoterms?: string; leadTimeDays?: number; countryOfOrigin?: string; condition?: string; warranty?: string; evidenceRefs?: { key: string; hash?: string }[] } }>("/", async (req, reply) => {
const orgId = app.orgId(req);
const vid = app.vendorId(req);
const vendorId = vid ?? req.body.vendorId ?? null;
if (!vendorId) throw app.httpErrors.badRequest("vendorId required (or login as vendor user)");
const [inserted] = await db.insert(offersTable).values({
orgId, vendorId, sku: req.body.sku ?? null, mpn: req.body.mpn ?? null, quantity: req.body.quantity, unitPrice: req.body.unitPrice,
incoterms: req.body.incoterms ?? null, leadTimeDays: req.body.leadTimeDays ?? null, countryOfOrigin: req.body.countryOfOrigin ?? null, condition: req.body.condition ?? null, warranty: req.body.warranty ?? null, evidenceRefs: req.body.evidenceRefs ?? null,
}).returning();
return reply.code(201).send(inserted);
});
app.patch<{ Params: { id: string }; Body: Partial<{ sku: string; mpn: string; quantity: number; unitPrice: string; status: string; evidenceRefs: { key: string; hash?: string }[] }> }>("/:id", async (req, reply) => {
const orgId = app.orgId(req);
const vid = app.vendorId(req);
const conditions = [eq(offersTable.id, req.params.id), eq(offersTable.orgId, orgId)] as ReturnType<typeof eq>[];
if (vid) conditions.push(eq(offersTable.vendorId, vid));
const [updated] = await db.update(offersTable).set({ ...req.body, updatedAt: new Date() }).where(and(...conditions)).returning();
if (!updated) return reply.notFound();
return reply.send(updated);
});
app.delete<{ Params: { id: string } }>("/:id", async (req, reply) => {
const orgId = app.orgId(req);
const vid = app.vendorId(req);
const conditions = [eq(offersTable.id, req.params.id), eq(offersTable.orgId, orgId)] as ReturnType<typeof eq>[];
if (vid) conditions.push(eq(offersTable.vendorId, vid));
const [deleted] = await db.delete(offersTable).where(and(...conditions)).returning({ id: offersTable.id });
if (!deleted) return reply.notFound();
return reply.code(204).send();
});
}