import { FastifyInstance } from "fastify" import { eq, ilike, asc, desc, and, count, inArray } from "drizzle-orm" import { vendors, contacts, files, } from "../../../db/schema" export default async function vendorRoutes(server: FastifyInstance) { // ------------------------------------------------------------- // LIST // ------------------------------------------------------------- server.get("/resource/vendors", async (req, reply) => { try { const tenantId = req.user?.tenant_id if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const { search, sort, asc: ascQuery } = req.query as { search?: string sort?: string asc?: string } let baseQuery = server.db .select() .from(vendors) .where(eq(vendors.tenant, tenantId)) // 🔎 Suche if (search) { baseQuery = server.db .select() .from(vendors) .where( and( eq(vendors.tenant, tenantId), ilike(vendors.name, `%${search}%`) ) ) } // 🔽 Sortierung if (sort) { const field = (vendors as any)[sort] if (field) { // @ts-ignore baseQuery = baseQuery.orderBy( ascQuery === "true" ? asc(field) : desc(field) ) } } return await baseQuery } catch (err) { console.error("ERROR /resource/vendors:", err) return reply.code(500).send({ error: "Internal Server Error" }) } }) // ------------------------------------------------------------- // PAGINATED // ------------------------------------------------------------- server.get("/resource/vendors/paginated", async (req, reply) => { try { const tenantId = req.user?.tenant_id if (!tenantId) { return reply.code(400).send({ error: "No tenant selected" }) } const queryConfig = req.queryConfig const { pagination, sort, filters, paginationDisabled } = queryConfig const { select, search, searchColumns, distinctColumns } = req.query as { select?: string search?: string searchColumns?: string distinctColumns?: string } // ---------------------------- // WHERE CONDITIONS // ---------------------------- let whereCond: any = eq(vendors.tenant, tenantId) if (filters) { for (const [key, val] of Object.entries(filters)) { const col = (vendors as any)[key] if (!col) continue if (Array.isArray(val)) { whereCond = and(whereCond, inArray(col, val)) } else if (val === true || val === false || val === null) { whereCond = and(whereCond, eq(col, val)) } else { whereCond = and(whereCond, eq(col, val)) } } } if (search && search.trim().length > 0) { whereCond = and( whereCond, ilike(vendors.name, `%${search.trim().toLowerCase()}%`) ) } // ---------------------------- // COUNT // ---------------------------- const totalRes = await server.db .select({ value: count(vendors.id) }) .from(vendors) .where(whereCond) const total = Number(totalRes[0]?.value ?? 0) // ---------------------------- // DISTINCT FILTER VALUES // ---------------------------- const distinctValues: Record = {} if (distinctColumns) { for (const colName of distinctColumns.split(",").map(v => v.trim())) { const col = (vendors as any)[colName] if (!col) continue const rows = await server.db .select({ v: col }) .from(vendors) .where(eq(vendors.tenant, tenantId)) const values = rows .map(r => r.v) .filter(v => v != null && v !== "") distinctValues[colName] = [...new Set(values)].sort() } } // ---------------------------- // PAGINATION // ---------------------------- let offset = 0 let limit = 999999 if (!paginationDisabled && pagination) { offset = pagination.offset limit = pagination.limit } // ---------------------------- // ORDER BY // ---------------------------- let orderField = null let orderDirection: "asc" | "desc" = "asc" if (sort?.length > 0) { const s = sort[0] const col = (vendors as any)[s.field] if (col) { orderField = col orderDirection = s.direction === "asc" ? "asc" : "desc" } } // ---------------------------- // QUERY DATA // ---------------------------- let dataQuery = server.db .select() .from(vendors) .where(whereCond) .offset(offset) .limit(limit) if (orderField) { dataQuery = orderDirection === "asc" ? dataQuery.orderBy(asc(orderField)) : dataQuery.orderBy(desc(orderField)) } const data = await dataQuery const totalPages = pagination?.limit ? Math.ceil(total / pagination.limit) : 1 const enrichedConfig = { ...queryConfig, total, totalPages, distinctValues, search: search || null, } return { data, queryConfig: enrichedConfig } } catch (err) { console.error("ERROR /resource/vendors/paginated:", err) return reply.code(500).send({ error: "Internal Server Error" }) } }) // ------------------------------------------------------------- // DETAIL (mit JOINS) // ------------------------------------------------------------- server.get("/resource/vendors/:id", async (req, reply) => { try { const { id } = req.params as { id: string } const tenantId = req.user?.tenant_id if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const vendorId = Number(id) const vendorRecord = await server.db .select() .from(vendors) .where( and( eq(vendors.id, vendorId), eq(vendors.tenant, tenantId) ) ) .limit(1) if (!vendorRecord.length) { return reply.code(404).send({ error: "Vendor not found" }) } const vendor = vendorRecord[0] // ---------------------------- // RELATIONS // ---------------------------- const [ vendorContacts, vendorFiles, ] = await Promise.all([ server.db .select() .from(contacts) .where(eq(contacts.vendor, vendorId)), server.db .select() .from(files) .where(eq(files.vendor, vendorId)), ]) return { ...vendor, contacts: vendorContacts, files: vendorFiles, } } catch (err) { console.error("ERROR /resource/vendors/:id:", err) return reply.code(500).send({ error: "Internal Server Error" }) } }) }