removed Routes, Introduced Single Route with Cecking
This commit is contained in:
413
src/routes/resources/main.ts
Normal file
413
src/routes/resources/main.ts
Normal file
@@ -0,0 +1,413 @@
|
||||
import { FastifyInstance } from "fastify"
|
||||
import {
|
||||
eq,
|
||||
ilike,
|
||||
asc,
|
||||
desc,
|
||||
and,
|
||||
count,
|
||||
inArray,
|
||||
or
|
||||
} from "drizzle-orm"
|
||||
|
||||
|
||||
import {
|
||||
projects,
|
||||
customers,
|
||||
plants,
|
||||
contracts,
|
||||
projecttypes,
|
||||
createddocuments,
|
||||
files,
|
||||
events,
|
||||
tasks, contacts, vendors
|
||||
} from "../../../db/schema"
|
||||
import * as sea from "node:sea";
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// SQL Volltextsuche auf mehreren Feldern
|
||||
// -------------------------------------------------------------
|
||||
|
||||
|
||||
function buildSearchCondition(table: any, columns: string[], search: string) {
|
||||
if (!search || !columns.length) return null
|
||||
|
||||
const term = `%${search.toLowerCase()}%`
|
||||
|
||||
const conditions = columns
|
||||
.map((colName) => table[colName])
|
||||
.filter(Boolean)
|
||||
.map((col) => ilike(col, term))
|
||||
|
||||
if (conditions.length === 0) return null
|
||||
|
||||
// @ts-ignore
|
||||
return or(...conditions)
|
||||
}
|
||||
|
||||
export default async function resourceRoutes(server: FastifyInstance) {
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// LIST
|
||||
// -------------------------------------------------------------
|
||||
/*server.get("/resource/:resource", 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
|
||||
}
|
||||
|
||||
// WHERE-Basis
|
||||
let whereCond: any = eq(projects.tenant, tenantId)
|
||||
|
||||
// 🔍 SQL Search
|
||||
const searchCond = buildProjectSearch(search)
|
||||
if (searchCond) whereCond = and(whereCond, searchCond)
|
||||
|
||||
// Base Query
|
||||
let q = server.db.select().from(projects).where(whereCond)
|
||||
|
||||
// Sortierung
|
||||
if (sort) {
|
||||
const col = (projects as any)[sort]
|
||||
if (col) {
|
||||
q = ascQuery === "true"
|
||||
? q.orderBy(asc(col))
|
||||
: q.orderBy(desc(col))
|
||||
}
|
||||
}
|
||||
|
||||
const data = await q
|
||||
return data
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERROR /resource/projects", err)
|
||||
return reply.code(500).send({ error: "Internal Server Error" })
|
||||
}
|
||||
})*/
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// PAGINATED LIST
|
||||
// -------------------------------------------------------------
|
||||
server.get("/resource/:resource/paginated", async (req, reply) => {
|
||||
try {
|
||||
const tenantId = req.user?.tenant_id;
|
||||
if (!tenantId) {
|
||||
return reply.code(400).send({ error: "No tenant selected" });
|
||||
}
|
||||
|
||||
const {resource} = req.params as {resource: string};
|
||||
|
||||
const {queryConfig} = req;
|
||||
const {
|
||||
pagination,
|
||||
sort,
|
||||
filters,
|
||||
paginationDisabled
|
||||
} = queryConfig;
|
||||
|
||||
const { search, distinctColumns } = req.query as {
|
||||
search?: string;
|
||||
distinctColumns?: string;
|
||||
};
|
||||
|
||||
|
||||
const config = {
|
||||
projects: {
|
||||
searchColumns: ["name"],
|
||||
mtoLoad: ["customer","plant","contract","projecttype"],
|
||||
table: projects
|
||||
},
|
||||
customers: {
|
||||
searchColumns: ["name", "customerNumber", "firstname", "lastname", "notes"],
|
||||
table: customers,
|
||||
},
|
||||
contacts: {
|
||||
searchColumns: ["firstName", "lastName", "email", "phone", "notes"],
|
||||
table: contacts,
|
||||
mtoLoad: ["customer","vendor"]
|
||||
},
|
||||
contracts: {
|
||||
table: contracts,
|
||||
searchColumns: ["name", "notes", "contractNumber", "paymentType", "sepaRef", "bankingName"]
|
||||
},
|
||||
plants: {
|
||||
table: plants
|
||||
},
|
||||
projecttypes: {
|
||||
table: projecttypes
|
||||
},
|
||||
vendors: {
|
||||
table: vendors,
|
||||
searchColumns: ["name","vendorNumber","notes","defaultPaymentType"],
|
||||
},
|
||||
files: {
|
||||
table: files
|
||||
}
|
||||
}
|
||||
|
||||
let table = config[resource].table
|
||||
|
||||
let whereCond: any = eq(table.tenant, tenantId);
|
||||
|
||||
|
||||
if(search) {
|
||||
const searchCond = buildSearchCondition(
|
||||
table,
|
||||
config[resource].searchColumns,
|
||||
search.trim()
|
||||
)
|
||||
|
||||
if (searchCond) {
|
||||
whereCond = and(whereCond, searchCond)
|
||||
}
|
||||
}
|
||||
|
||||
if (filters) {
|
||||
for (const [key, val] of Object.entries(filters)) {
|
||||
const col = (table as any)[key];
|
||||
if (!col) continue;
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
whereCond = and(whereCond, inArray(col, val));
|
||||
} else {
|
||||
whereCond = and(whereCond, eq(col, val as any));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// COUNT (for pagination)
|
||||
// -----------------------------------------------
|
||||
const totalRes = await server.db
|
||||
.select({ value: count(table.id) })
|
||||
.from(table)
|
||||
.where(whereCond);
|
||||
|
||||
const total = Number(totalRes[0]?.value ?? 0);
|
||||
|
||||
// -----------------------------------------------
|
||||
// DISTINCT VALUES (regardless of pagination)
|
||||
// -----------------------------------------------
|
||||
const distinctValues: Record<string, any[]> = {};
|
||||
|
||||
if (distinctColumns) {
|
||||
for (const colName of distinctColumns.split(",").map(c => c.trim())) {
|
||||
const col = (table as any)[colName];
|
||||
if (!col) continue;
|
||||
|
||||
const rows = await server.db
|
||||
.select({ v: col })
|
||||
.from(table)
|
||||
.where(eq(table.tenant, tenantId));
|
||||
|
||||
const values = rows
|
||||
.map(r => r.v)
|
||||
.filter(v => v != null && v !== "");
|
||||
|
||||
distinctValues[colName] = [...new Set(values)].sort();
|
||||
}
|
||||
}
|
||||
|
||||
// PAGINATION
|
||||
const offset = pagination?.offset ?? 0;
|
||||
const limit = pagination?.limit ?? 100;
|
||||
|
||||
// SORTING
|
||||
let orderField: any = null;
|
||||
let direction: "asc" | "desc" = "asc";
|
||||
|
||||
if (sort?.length > 0) {
|
||||
const s = sort[0];
|
||||
const col = (projects as any)[s.field];
|
||||
if (col) {
|
||||
orderField = col;
|
||||
direction = s.direction === "asc" ? "asc" : "desc";
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN QUERY (Paginated)
|
||||
let q = server.db
|
||||
.select()
|
||||
.from(table)
|
||||
.where(whereCond)
|
||||
.offset(offset)
|
||||
.limit(limit);
|
||||
|
||||
if (orderField) {
|
||||
//@ts-ignore
|
||||
q = direction === "asc"
|
||||
? q.orderBy(asc(orderField))
|
||||
: q.orderBy(desc(orderField));
|
||||
}
|
||||
|
||||
const rows = await q;
|
||||
|
||||
if (!rows.length) {
|
||||
return {
|
||||
data: [],
|
||||
queryConfig: {
|
||||
...queryConfig,
|
||||
total,
|
||||
totalPages: 0,
|
||||
distinctValues
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// RELATION LOADING (MANY-TO-ONE)
|
||||
|
||||
let ids = {}
|
||||
let lists = {}
|
||||
let maps = {}
|
||||
let data = []
|
||||
|
||||
if(config[resource].mtoLoad) {
|
||||
config[resource].mtoLoad.forEach(relation => {
|
||||
ids[relation] = [...new Set(rows.map(r => r[relation]).filter(Boolean))];
|
||||
})
|
||||
|
||||
for await (const relation of config[resource].mtoLoad ) {
|
||||
lists[relation] = ids[relation].length ? await server.db.select().from(config[relation + "s"].table).where(inArray(config[relation + "s"].table.id, ids[relation])) : []
|
||||
|
||||
}
|
||||
|
||||
config[resource].mtoLoad.forEach(relation => {
|
||||
maps[relation] = Object.fromEntries(lists[relation].map(i => [i.id, i]));
|
||||
})
|
||||
|
||||
data = rows.map(row => {
|
||||
let toReturn = {
|
||||
...row
|
||||
}
|
||||
|
||||
config[resource].mtoLoad.forEach(relation => {
|
||||
toReturn[relation] = row[relation] ? maps[relation][row[relation]] : null
|
||||
})
|
||||
|
||||
return toReturn
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------
|
||||
// RETURN DATA
|
||||
// -----------------------------------------------
|
||||
return {
|
||||
data,
|
||||
queryConfig: {
|
||||
...queryConfig,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
distinctValues
|
||||
}
|
||||
};
|
||||
|
||||
} catch (err) {
|
||||
console.error(`ERROR /resource/:resource/paginated:`, err);
|
||||
return reply.code(500).send({ error: "Internal Server Error" });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// DETAIL (mit JOINS)
|
||||
// -------------------------------------------------------------
|
||||
/*server.get("/resource/projects/: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 pid = Number(id)
|
||||
|
||||
const projRows = await server.db
|
||||
.select()
|
||||
.from(projects)
|
||||
.where(and(eq(projects.id, pid), eq(projects.tenant, tenantId)))
|
||||
.limit(1)
|
||||
|
||||
if (!projRows.length)
|
||||
return reply.code(404).send({ error: "Project not found" })
|
||||
|
||||
const project = projRows[0]
|
||||
|
||||
// ------------------------------------
|
||||
// LOAD RELATIONS
|
||||
// ------------------------------------
|
||||
const [
|
||||
customerRecord,
|
||||
plantRecord,
|
||||
contractRecord,
|
||||
projectTypeRecord,
|
||||
projectTasks,
|
||||
projectFiles,
|
||||
projectDocuments,
|
||||
projectEvents,
|
||||
] = await Promise.all([
|
||||
|
||||
project.customer
|
||||
? server.db.select().from(customers).where(eq(customers.id, project.customer))
|
||||
: [],
|
||||
|
||||
project.plant
|
||||
? server.db.select().from(plants).where(eq(plants.id, project.plant))
|
||||
: [],
|
||||
|
||||
project.contract
|
||||
? server.db.select().from(contracts).where(eq(contracts.id, project.contract))
|
||||
: [],
|
||||
|
||||
project.projecttype
|
||||
? server.db.select().from(projecttypes).where(eq(projecttypes.id, project.projecttype))
|
||||
: [],
|
||||
|
||||
// Tasks
|
||||
server.db
|
||||
.select()
|
||||
.from(tasks)
|
||||
.where(eq(tasks.project, pid)),
|
||||
|
||||
// Files
|
||||
server.db
|
||||
.select()
|
||||
.from(files)
|
||||
.where(eq(files.project, pid)),
|
||||
|
||||
// Documents
|
||||
server.db
|
||||
.select()
|
||||
.from(createddocuments)
|
||||
.where(eq(createddocuments.project, pid)),
|
||||
|
||||
// Events
|
||||
server.db
|
||||
.select()
|
||||
.from(events)
|
||||
.where(eq(events.project, pid)),
|
||||
|
||||
])
|
||||
|
||||
return {
|
||||
...project,
|
||||
customer: customerRecord[0] ?? null,
|
||||
plant: plantRecord[0] ?? null,
|
||||
contract: contractRecord[0] ?? null,
|
||||
projecttype: projectTypeRecord[0] ?? null,
|
||||
tasks: projectTasks,
|
||||
files: projectFiles,
|
||||
createddocuments: projectDocuments,
|
||||
events: projectEvents,
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error("ERROR /resource/projects/:id", err)
|
||||
return reply.code(500).send({ error: "Internal Server Error" })
|
||||
}
|
||||
})*/
|
||||
}
|
||||
Reference in New Issue
Block a user