import { Hono } from 'hono' import { cors } from 'hono/cors' import { logger } from 'hono/logger' import { trpcServer } from '@hono/trpc-server' import { getStaffUserById, isUserSuspended } from '@/src/dbService' import mainRouter from '@/src/main-router' import { appRouter } from '@/src/trpc/router' import { TRPCError } from '@trpc/server' import { jwtVerify } from 'jose' import { getEncodedJwtSecret } from '@/src/lib/env-exporter' export const createApp = () => { const app = new Hono() // CORS middleware app.use(cors({ origin: ['http://localhost:5174', 'https://ui.freshyo.in'], allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'], credentials: true, })) // Logger middleware app.use(logger()) // tRPC middleware app.use('/api/trpc/*', trpcServer({ router: appRouter, createContext: async ({ req }) => { let user = null let staffUser = null const authHeader = req.headers.get('authorization') if (authHeader?.startsWith('Bearer ')) { const token = authHeader.substring(7) try { const { payload } = await jwtVerify(token, getEncodedJwtSecret()) const decoded = payload as any // Check if this is a staff token (has staffId) if (decoded.staffId) { // This is a staff token, verify staff exists const staff = await getStaffUserById(decoded.staffId) if (staff) { user = staffUser staffUser = { id: staff.id, name: staff.name, } } } else { // This is a regular user token user = decoded // Check if user is suspended const suspended = await isUserSuspended(user.userId) if (suspended) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Account suspended', }) } } } catch (err) { // Invalid token, both user and staffUser remain null } } return { req, user, staffUser } }, onError({ error, path, type, ctx }) { console.error('🚨 tRPC Error :', { path, type, code: error.code, message: error.message, userId: ctx?.user?.userId, stack: error.stack, }) }, })) // Mount main router app.route('/api', mainRouter) // Global error handler app.onError((err, c) => { console.error(err) // Handle different error types let status = 500 let message = 'Internal Server Error' if (err instanceof TRPCError) { // Map TRPC error codes to HTTP status codes const trpcStatusMap: Record = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, TIMEOUT: 408, CONFLICT: 409, PRECONDITION_FAILED: 412, PAYLOAD_TOO_LARGE: 413, METHOD_NOT_SUPPORTED: 405, UNPROCESSABLE_CONTENT: 422, TOO_MANY_REQUESTS: 429, INTERNAL_SERVER_ERROR: 500, } status = trpcStatusMap[err.code] || 500 message = err.message } else if ((err as any).statusCode) { status = (err as any).statusCode message = err.message } else if ((err as any).status) { status = (err as any).status message = err.message } else if (err.message) { message = err.message } return c.json({ message }, status as any) }) return app }