freshyo/apps/backend/src/app.ts
2026-04-08 23:31:55 +05:30

126 lines
3.5 KiB
TypeScript

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<string, number> = {
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
}