// index.ts import express from "express"; import cors from "cors"; import cookieParser from "cookie-parser"; import * as trpcExpress from "@trpc/server/adapters/express"; // trpc.ts import { createQueryBuilder } from "@taylordb/query-builder"; import { initTRPC } from "@trpc/server"; var createContext = ({ req, res }) => { const appAccessToken = req.cookies?.app_access_token; if (!appAccessToken) { throw new Error("Unauthorized: app_access_token cookie is required"); } const queryBuilder = createQueryBuilder({ baseUrl: process.env.TAYLORDB_BASE_URL, baseId: process.env.TAYLORDB_SERVER_ID, apiKey: appAccessToken }); return { req, res, queryBuilder }; }; var t = initTRPC.context().create(); var router = t.router; var publicProcedure = t.procedure; // routers/companies.ts import { z } from "zod"; var companiesRouter = router({ getAll: publicProcedure.query(async ({ ctx }) => { return await ctx.queryBuilder.selectFrom("companies").select(["id", "name", "website", "notes", "createdAt", "updatedAt"]).orderBy("createdAt", "desc").execute(); }), search: publicProcedure.input(z.object({ query: z.string(), limit: z.number().default(5) })).query(async ({ ctx, input }) => { const searchTerm = input.query.trim(); if (!searchTerm) { return await ctx.queryBuilder.selectFrom("companies").select(["id", "name", "website"]).orderBy("createdAt", "desc").limit(input.limit).execute(); } return await ctx.queryBuilder.selectFrom("companies").select(["id", "name", "website"]).where("name", "contains", searchTerm).orWhere("website", "contains", searchTerm).orderBy("createdAt", "desc").limit(input.limit).execute(); }), getById: publicProcedure.input(z.object({ id: z.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("companies").select(["id", "name", "website", "notes", "createdAt", "updatedAt"]).where("id", "=", input.id).executeTakeFirst(); }), create: publicProcedure.input( z.object({ name: z.string().min(1), website: z.string().optional(), notes: z.string().optional() }) ).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.insertInto("companies").values(input).executeTakeFirst(); }), update: publicProcedure.input( z.object({ id: z.number(), name: z.string().optional(), website: z.string().optional(), notes: z.string().optional() }) ).mutation(async ({ ctx, input }) => { const { id, ...data } = input; return await ctx.queryBuilder.update("companies").set(data).where("id", "=", id).execute(); }), delete: publicProcedure.input(z.object({ id: z.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.deleteFrom("companies").where("id", "=", input.id).execute(); }) }); // routers/contacts.ts import { z as z2 } from "zod"; var contactsRouter = router({ getAll: publicProcedure.query(async ({ ctx }) => { return await ctx.queryBuilder.selectFrom("contacts").select(["id", "firstName", "lastName", "mail", "phone1", "phone2", "notes", "createdAt", "updatedAt"]).orderBy("createdAt", "desc").execute(); }), search: publicProcedure.input(z2.object({ query: z2.string(), limit: z2.number().default(5) })).query(async ({ ctx, input }) => { const searchTerm = input.query.trim(); if (!searchTerm) { return await ctx.queryBuilder.selectFrom("contacts").select(["id", "firstName", "lastName", "mail"]).orderBy("createdAt", "desc").limit(input.limit).execute(); } return await ctx.queryBuilder.selectFrom("contacts").select(["id", "firstName", "lastName", "mail"]).where("firstName", "contains", searchTerm).orWhere("lastName", "contains", searchTerm).orWhere("mail", "contains", searchTerm).orderBy("createdAt", "desc").limit(input.limit).execute(); }), getById: publicProcedure.input(z2.object({ id: z2.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("contacts").select(["id", "firstName", "lastName", "mail", "phone1", "phone2", "notes", "createdAt", "updatedAt"]).with({ company: (qb) => qb.select(["id", "name", "website"]) }).where("id", "=", input.id).executeTakeFirst(); }), create: publicProcedure.input( z2.object({ firstName: z2.string().min(1), lastName: z2.string().min(1), mail: z2.string().email().optional().or(z2.literal("")), phone1: z2.string().optional(), phone2: z2.string().optional(), notes: z2.string().optional(), company: z2.array(z2.number()).optional() }) ).mutation(async ({ ctx, input }) => { const values = { ...input, mail: input.mail || void 0 }; return await ctx.queryBuilder.insertInto("contacts").values(values).executeTakeFirst(); }), update: publicProcedure.input( z2.object({ id: z2.number(), firstName: z2.string().optional(), lastName: z2.string().optional(), mail: z2.string().email().optional().or(z2.literal("")), phone1: z2.string().optional(), phone2: z2.string().optional(), notes: z2.string().optional(), company: z2.array(z2.number()).optional() }) ).mutation(async ({ ctx, input }) => { const { id, ...data } = input; const updateData = Object.fromEntries( Object.entries(data).filter(([key, v]) => { if (v === void 0) return false; if (key === "mail" && v === "") return false; return true; }) ); return await ctx.queryBuilder.update("contacts").set(updateData).where("id", "=", id).execute(); }), delete: publicProcedure.input(z2.object({ id: z2.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.deleteFrom("contacts").where("id", "=", input.id).execute(); }) }); // routers/deals.ts import { z as z3 } from "zod"; var DealsPhaseOptions = [ "New", "Demo scheduled", "Contract signed", "Lost" ]; var phaseEnum = z3.enum(DealsPhaseOptions); var dealsRouter = router({ getAll: publicProcedure.query(async ({ ctx }) => { return await ctx.queryBuilder.selectFrom("deals").select(["id", "name", "phase", "createdAt", "updatedAt"]).with({ mainContact: (qb) => qb.select(["id", "firstName", "lastName", "mail"]) }).orderBy("createdAt", "desc").execute(); }), getById: publicProcedure.input(z3.object({ id: z3.number() })).query(async ({ ctx, input }) => { const deal = await ctx.queryBuilder.selectFrom("deals").select(["id", "name", "phase", "createdAt", "updatedAt"]).with({ mainContact: (qb) => qb.select(["id", "firstName", "lastName", "mail"]) }).where("id", "=", input.id).executeTakeFirst(); return deal; }), search: publicProcedure.input(z3.object({ query: z3.string(), limit: z3.number().optional() })).query(async ({ ctx, input }) => { let query = ctx.queryBuilder.selectFrom("deals").select(["id", "name", "phase"]); if (input.query.trim()) { query = query.where("name", "contains", input.query); } return await query.orderBy("name", "asc").execute(); }), // Get detailed deal info with contact and company for details page getDetails: publicProcedure.input(z3.object({ id: z3.number() })).query(async ({ ctx, input }) => { const deal = await ctx.queryBuilder.selectFrom("deals").select(["id", "name", "phase", "lostReason", "createdAt", "updatedAt"]).with({ mainContact: (qb) => qb.select(["id", "firstName", "lastName", "mail", "phone1", "phone2", "notes"]).with({ company: (companyQb) => companyQb.select(["id", "name", "website", "notes"]) }) }).with({ activities: (qb) => qb.select(["id", "name", "type", "plannedAt", "doneAt", "createdAt"]).with({ notes: (notesQb) => notesQb.select(["id", "name", "createdAt"]) }) }).where("id", "=", input.id).executeTakeFirst(); return deal; }), create: publicProcedure.input( z3.object({ name: z3.string().min(1), phase: phaseEnum.default("New"), mainContact: z3.array(z3.number()).optional() }) ).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.insertInto("deals").values(input).executeTakeFirst(); }), update: publicProcedure.input( z3.object({ id: z3.number(), name: z3.string().optional(), phase: phaseEnum.optional(), mainContact: z3.array(z3.number()).optional() }) ).mutation(async ({ ctx, input }) => { const { id, ...data } = input; const updateData = Object.fromEntries( Object.entries(data).filter(([, v]) => v !== void 0) ); return await ctx.queryBuilder.update("deals").set(updateData).where("id", "=", id).execute(); }), delete: publicProcedure.input(z3.object({ id: z3.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.deleteFrom("deals").where("id", "=", input.id).execute(); }) }); // routers/activities.ts import { z as z4 } from "zod"; var ActivitiesTypeOptions = [ "Call", "Meeting", "Task", "Demo" ]; var activitiesRouter = router({ getAll: publicProcedure.input( z4.object({ dateFilter: z4.enum(["all", "today", "thisWeek", "thisMonth"]).optional(), typeFilter: z4.enum(["all", ...ActivitiesTypeOptions]).optional() }).optional() ).query(async ({ ctx, input }) => { let query = ctx.queryBuilder.selectFrom("activities").select(["id", "name", "type", "plannedAt", "doneAt", "createdAt", "updatedAt"]).with({ deal: (qb) => qb.select(["id", "name"]) }).with({ notes: (qb) => qb.select(["id", "name"]) }); if (input?.dateFilter && input.dateFilter !== "all") { if (input.dateFilter === "today") { query = query.where("plannedAt", "=", "today"); } else if (input.dateFilter === "thisWeek") { query = query.where("plannedAt", "isWithIn", "currentWeek"); } else if (input.dateFilter === "thisMonth") { query = query.where("plannedAt", "isWithIn", "currentMonth"); } } if (input?.typeFilter && input.typeFilter !== "all") { query = query.where("type", "=", input.typeFilter); } return await query.orderBy("plannedAt", "desc").execute(); }), getByDeal: publicProcedure.input(z4.object({ dealId: z4.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("activities").select(["id", "name", "type", "plannedAt", "doneAt", "createdAt", "updatedAt"]).with({ notes: (qb) => qb.select(["id", "name"]) }).where("deal", "=", input.dealId).orderBy("plannedAt", "asc").execute(); }), getById: publicProcedure.input(z4.object({ id: z4.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("activities").select(["id", "name", "type", "plannedAt", "doneAt", "createdAt", "updatedAt"]).with({ deal: (qb) => qb.select(["id", "name"]) }).with({ notes: (qb) => qb.select(["id", "name"]) }).where("id", "=", input.id).executeTakeFirst(); }), create: publicProcedure.input( z4.object({ name: z4.string().min(1), deal: z4.array(z4.number()).optional(), plannedAt: z4.string().optional(), doneAt: z4.string().optional(), type: z4.enum(ActivitiesTypeOptions).optional() }) ).mutation(async ({ ctx, input }) => { const values = { name: input.name }; if (input.deal) values.deal = input.deal; if (input.plannedAt) values.plannedAt = input.plannedAt; if (input.doneAt) values.doneAt = input.doneAt; if (input.type) values.type = input.type; return await ctx.queryBuilder.insertInto("activities").values(values).executeTakeFirst(); }), update: publicProcedure.input( z4.object({ id: z4.number(), name: z4.string().optional(), plannedAt: z4.string().optional(), doneAt: z4.string().optional(), notes: z4.array(z4.number()).optional(), type: z4.enum(ActivitiesTypeOptions).optional() }) ).mutation(async ({ ctx, input }) => { const { id, ...data } = input; const updateData = Object.fromEntries( Object.entries(data).filter(([, v]) => v !== void 0) ); return await ctx.queryBuilder.update("activities").set(updateData).where("id", "=", id).execute(); }), complete: publicProcedure.input(z4.object({ id: z4.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.update("activities").set({ doneAt: (/* @__PURE__ */ new Date()).toISOString() }).where("id", "=", input.id).execute(); }), uncomplete: publicProcedure.input(z4.object({ id: z4.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.update("activities").set({ doneAt: null }).where("id", "=", input.id).execute(); }), delete: publicProcedure.input(z4.object({ id: z4.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.deleteFrom("activities").where("id", "=", input.id).execute(); }) }); // routers/notes.ts import { z as z5 } from "zod"; var notesRouter = router({ getByActivity: publicProcedure.input(z5.object({ activityId: z5.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("notes").select(["id", "name", "createdAt", "updatedAt"]).where("activities1", "=", input.activityId).orderBy("createdAt", "desc").execute(); }), getById: publicProcedure.input(z5.object({ id: z5.number() })).query(async ({ ctx, input }) => { return await ctx.queryBuilder.selectFrom("notes").select(["id", "name", "createdAt", "updatedAt"]).where("id", "=", input.id).executeTakeFirst(); }), create: publicProcedure.input( z5.object({ name: z5.string().min(1), activities1: z5.array(z5.number()) }) ).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.insertInto("notes").values(input).executeTakeFirst(); }), update: publicProcedure.input( z5.object({ id: z5.number(), name: z5.string().optional() }) ).mutation(async ({ ctx, input }) => { const { id, ...data } = input; const updateData = Object.fromEntries( Object.entries(data).filter(([, v]) => v !== void 0) ); return await ctx.queryBuilder.update("notes").set(updateData).where("id", "=", id).execute(); }), delete: publicProcedure.input(z5.object({ id: z5.number() })).mutation(async ({ ctx, input }) => { return await ctx.queryBuilder.deleteFrom("notes").where("id", "=", input.id).execute(); }) }); // router.ts var appRouter = router({ companies: companiesRouter, contacts: contactsRouter, deals: dealsRouter, activities: activitiesRouter, notes: notesRouter }); // index.ts var app = express(); var PORT = process.env.PORT || 3001; app.use(cookieParser()); app.use( cors({ origin: [ process.env.FRONTEND_URL || "http://localhost:5173", "http://localhost:5174" ], credentials: true }) ); app.get("/api/health", (_req, res) => { res.json({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() }); }); app.use( "/api/trpc", (req, res, next) => { if (req.path === "/" || req.path === "") { return res.json({ message: "TaylorDB tRPC server is running!", health: `http://${req.headers.host}/api/trpc/health`, timestamp: (/* @__PURE__ */ new Date()).toISOString() }); } next(); }, trpcExpress.createExpressMiddleware({ router: appRouter, createContext }) ); app.listen(PORT, () => { console.log(`\u{1F680} Server running on http://localhost:${PORT}`); console.log(`\u{1F4E1} tRPC endpoint: http://localhost:${PORT}/api/trpc`); });