176 lines
5 KiB
TypeScript
Executable file
176 lines
5 KiB
TypeScript
Executable file
import 'dotenv/config';
|
|
import express, { NextFunction, Request, Response } from "express";
|
|
import cors from "cors";
|
|
// import bodyParser from "body-parser";
|
|
import multer from "multer";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
import { db } from './src/db/db_index';
|
|
import { staffUsers, userDetails } from './src/db/schema';
|
|
import { eq } from 'drizzle-orm';
|
|
import mainRouter from './src/main-router';
|
|
import initFunc from './src/lib/init';
|
|
import { createExpressMiddleware } from '@trpc/server/adapters/express';
|
|
import { appRouter } from './src/trpc/router';
|
|
import { TRPCError } from '@trpc/server';
|
|
import jwt from 'jsonwebtoken'
|
|
import signedUrlCache from 'src/lib/signed-url-cache';
|
|
import { seed } from 'src/db/seed';
|
|
import './src/jobs/jobs-index';
|
|
import { startAutomatedJobs } from './src/lib/automatedJobs';
|
|
|
|
seed()
|
|
initFunc()
|
|
startAutomatedJobs()
|
|
|
|
const app = express();
|
|
|
|
app.use(cors({
|
|
origin: 'http://localhost:5174'
|
|
}));
|
|
|
|
|
|
signedUrlCache.loadFromDisk();
|
|
|
|
app.use(express.json());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Middleware to log all request URLs
|
|
app.use((req, res, next) => {
|
|
const timestamp = new Date().toISOString();
|
|
console.log(`[${timestamp}] ${req.method} ${req.url}`);
|
|
next();
|
|
});
|
|
|
|
//cors middleware
|
|
export function corsMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
// Allow requests from any origin (for production, replace * with your domain)
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
|
|
// Allow specific headers clients can send
|
|
res.header(
|
|
'Access-Control-Allow-Headers',
|
|
'Origin, X-Requested-With, Content-Type, Accept, Authorization'
|
|
);
|
|
|
|
// Allow specific HTTP methods
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
|
|
// Allow credentials if needed (optional)
|
|
// res.header('Access-Control-Allow-Credentials', 'true');
|
|
|
|
// Handle preflight (OPTIONS) requests quickly
|
|
if (req.method === 'OPTIONS') {
|
|
return res.sendStatus(204);
|
|
}
|
|
|
|
next();
|
|
}
|
|
|
|
|
|
app.use('/api/trpc', createExpressMiddleware({
|
|
router: appRouter,
|
|
createContext: async ({ req, res }) => {
|
|
let user = null;
|
|
let staffUser = null;
|
|
const authHeader = req.headers.authorization;
|
|
|
|
if (authHeader?.startsWith('Bearer ')) {
|
|
const token = authHeader.substring(7);
|
|
try {
|
|
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') 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 db.query.staffUsers.findFirst({
|
|
where: eq(staffUsers.id, 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 details = await db.query.userDetails.findFirst({
|
|
where: eq(userDetails.userId, user.userId),
|
|
});
|
|
|
|
if (details?.isSuspended) {
|
|
throw new TRPCError({
|
|
code: 'FORBIDDEN',
|
|
message: 'Account suspended',
|
|
});
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Invalid token, both user and staffUser remain null
|
|
}
|
|
}
|
|
return { req, res, 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,
|
|
});
|
|
},
|
|
}));
|
|
|
|
app.use('/api', mainRouter)
|
|
|
|
const fallbackUiDirCandidates = [
|
|
path.resolve(__dirname, '../fallback-ui/dist'),
|
|
path.resolve(__dirname, '../../fallback-ui/dist'),
|
|
path.resolve(process.cwd(), '../fallback-ui/dist'),
|
|
path.resolve(process.cwd(), './apps/fallback-ui/dist')
|
|
]
|
|
|
|
const fallbackUiDir =
|
|
fallbackUiDirCandidates.find((candidate) => fs.existsSync(candidate)) ??
|
|
fallbackUiDirCandidates[0]
|
|
|
|
|
|
const fallbackUiIndex = path.join(fallbackUiDir, 'index.html')
|
|
// const fallbackUiMountPath = '/admin-web'
|
|
const fallbackUiMountPath = '/';
|
|
|
|
if (fs.existsSync(fallbackUiIndex)) {
|
|
app.use(fallbackUiMountPath, express.static(fallbackUiDir))
|
|
app.use('/mf'+fallbackUiMountPath, express.static(fallbackUiDir))
|
|
const fallbackUiRegex = new RegExp(
|
|
`^${fallbackUiMountPath.replace(/\//g, '\\/')}(?:\\/.*)?$`
|
|
)
|
|
app.get([fallbackUiMountPath, fallbackUiRegex], (req, res) => {
|
|
res.sendFile(fallbackUiIndex)
|
|
})
|
|
app.get(/.*/, (req,res) => {
|
|
res.sendFile(fallbackUiIndex)
|
|
})
|
|
} else {
|
|
console.warn(`Fallback UI build not found at ${fallbackUiIndex}`)
|
|
}
|
|
|
|
// Global error handler
|
|
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
console.error(err);
|
|
const status = err.statusCode || err.status || 500;
|
|
const message = err.message || 'Internal Server Error';
|
|
res.status(status).json({ message });
|
|
});
|
|
|
|
app.listen(4000, () => {
|
|
console.log("Server is running on http://localhost:4000/api/mobile/");
|
|
});
|