import { Hono } from 'hono' import { cors } from 'hono/cors' import { logger } from 'hono/logger' import { serve } from 'bun' import { fetchRequestHandler } from '@trpc/server/adapters/fetch' import { appRouter } from '@/src/trpc/router' import { verifyToken, UserJWTPayload, StaffJWTPayload } from '@/src/lib/jwt-utils' import { db } from '@/src/db/db_index' import { staffUsers, userDetails } from '@/src/db/schema' import { eq } from 'drizzle-orm' import { TRPCError } from '@trpc/server' import signedUrlCache from '@/src/lib/signed-url-cache' import { seed } from '@/src/db/seed' import initFunc from '@/src/lib/init' import '@/src/jobs/jobs-index' import { startAutomatedJobs } from '@/src/lib/automatedJobs' // Initialize seed() initFunc() startAutomatedJobs() const app = new Hono() // CORS middleware app.use('*', cors({ origin: ['http://localhost:5174', 'http://localhost:5173'], allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'], credentials: true })) // Request logging app.use('*', logger()) // Health check app.get('/health', (c) => { return c.json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), message: 'Hello world' }) }) // tRPC handler with context app.use('/api/trpc/*', async (c) => { const response = await fetchRequestHandler({ endpoint: '/api/trpc', req: c.req.raw, 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 decoded = await verifyToken(token) if ('staffId' in decoded) { const staffPayload = decoded as StaffJWTPayload const staff = await db.query.staffUsers.findFirst({ where: eq(staffUsers.id, staffPayload.staffId) }) if (staff) { staffUser = { id: staff.id, name: staff.name } } } else { const userPayload = decoded as UserJWTPayload user = { userId: userPayload.userId, name: userPayload.name, email: userPayload.email, mobile: userPayload.mobile } const details = await db.query.userDetails.findFirst({ where: eq(userDetails.userId, userPayload.userId) }) if (details?.isSuspended) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Account suspended' }) } } } catch { // Invalid token } } return { req, res: c.res, user, staffUser } }, onError: ({ error, path, ctx }) => { console.error('🚨 tRPC Error:', { path, code: error.code, message: error.message, userId: ctx?.user?.userId }) } }) return response }) // Static files - Fallback UI app.use('/*', async (c) => { const url = new URL(c.req.url) let filePath = url.pathname // Default to index.html for root if (filePath === '/') { filePath = '/index.html' } // Try to serve the file const file = Bun.file(`./fallback-ui/dist${filePath}`) if (await file.exists()) { return new Response(file) } // SPA fallback - serve index.html for any unmatched routes const indexFile = Bun.file('./fallback-ui/dist/index.html') if (await indexFile.exists()) { return new Response(indexFile) } return c.notFound() }) // Static files - Assets app.use('/assets/*', async (c) => { const path = c.req.path.replace('/assets/', '') const file = Bun.file(`./assets/public/${path}`) if (await file.exists()) { return new Response(file) } return c.notFound() }) // Global error handler app.onError((err, c) => { console.error('Error:', err) const status = err instanceof TRPCError ? (err.code === 'UNAUTHORIZED' ? 401 : 500) : 500 const message = err.message || 'Internal Server Error' return c.json({ message }, status) }) // Start server serve({ fetch: app.fetch, port: 4000, hostname: '0.0.0.0' }) console.log('🚀 Server running on http://localhost:4000')