freshyo/apps/backend/index.ts
2026-03-27 00:34:32 +05:30

143 lines
3.9 KiB
TypeScript
Executable file

import 'dotenv/config';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { serve } from '@hono/node-server';
import { trpcServer } from '@hono/trpc-server';
import { getStaffUserById, isUserSuspended } from '@/src/dbService';
import mainRouter from '@/src/main-router';
import initFunc from '@/src/lib/init';
import { appRouter } from '@/src/trpc/router';
import { TRPCError } from '@trpc/server';
import { jwtVerify } from 'jose'
import { encodedJwtSecret } from '@/src/lib/env-exporter';
import signedUrlCache from '@/src/lib/signed-url-cache';
import { seed } from '@/src/lib/seed';
import '@/src/jobs/jobs-index';
import { startAutomatedJobs } from '@/src/lib/automatedJobs';
seed()
initFunc()
startAutomatedJobs()
const app = new Hono();
// CORS middleware
app.use(cors({
origin: 'http://localhost:5174',
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'],
credentials: true,
}));
signedUrlCache.loadFromDisk();
// 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, encodedJwtSecret);
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);
});
serve({
fetch: app.fetch,
port: 4000,
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}/api/mobile/`);
});