This commit is contained in:
shafi54 2026-03-27 00:34:32 +05:30
parent 639428caba
commit 1b042819af
18 changed files with 303 additions and 691 deletions

View file

@ -1,13 +1,12 @@
import 'dotenv/config'; import 'dotenv/config';
import express, { NextFunction, Request, Response } from "express"; import { Hono } from 'hono';
import cors from "cors"; import { cors } from 'hono/cors';
// import bodyParser from "body-parser"; import { logger } from 'hono/logger';
import path from "path"; import { serve } from '@hono/node-server';
import fs from "fs"; import { trpcServer } from '@hono/trpc-server';
import { getStaffUserById, getUserDetailsByUserId, isUserSuspended } from '@/src/dbService'; import { getStaffUserById, isUserSuspended } from '@/src/dbService';
import mainRouter from '@/src/main-router'; import mainRouter from '@/src/main-router';
import initFunc from '@/src/lib/init'; import initFunc from '@/src/lib/init';
import { createExpressMiddleware } from '@trpc/server/adapters/express';
import { appRouter } from '@/src/trpc/router'; import { appRouter } from '@/src/trpc/router';
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { jwtVerify } from 'jose' import { jwtVerify } from 'jose'
@ -21,57 +20,28 @@ seed()
initFunc() initFunc()
startAutomatedJobs() startAutomatedJobs()
const app = express(); const app = new Hono();
// CORS middleware
app.use(cors({ app.use(cors({
origin: 'http://localhost:5174' origin: 'http://localhost:5174',
allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization'],
credentials: true,
})); }));
signedUrlCache.loadFromDisk(); signedUrlCache.loadFromDisk();
app.use(express.json()); // Logger middleware
app.use(express.urlencoded({ extended: true })); app.use(logger());
// Middleware to log all request URLs // tRPC middleware
app.use((req, res, next) => { app.use('/api/trpc', trpcServer({
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, router: appRouter,
createContext: async ({ req, res }) => { createContext: async ({ req }) => {
let user = null; let user = null;
let staffUser = null; let staffUser = null;
const authHeader = req.headers.authorization; const authHeader = req.headers.get('authorization');
if (authHeader?.startsWith('Bearer ')) { if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7); const token = authHeader.substring(7);
@ -85,7 +55,7 @@ app.use('/api/trpc', createExpressMiddleware({
const staff = await getStaffUserById(decoded.staffId); const staff = await getStaffUserById(decoded.staffId);
if (staff) { if (staff) {
user=staffUser user = staffUser
staffUser = { staffUser = {
id: staff.id, id: staff.id,
name: staff.name, name: staff.name,
@ -110,7 +80,7 @@ app.use('/api/trpc', createExpressMiddleware({
// Invalid token, both user and staffUser remain null // Invalid token, both user and staffUser remain null
} }
} }
return { req, res, user, staffUser }; return { req, user, staffUser };
}, },
onError({ error, path, type, ctx }) { onError({ error, path, type, ctx }) {
console.error('🚨 tRPC Error :', { console.error('🚨 tRPC Error :', {
@ -121,60 +91,53 @@ app.use('/api/trpc', createExpressMiddleware({
userId: ctx?.user?.userId, userId: ctx?.user?.userId,
stack: error.stack, stack: error.stack,
}); });
}, },
})); }));
app.use('/api', mainRouter) // Mount main router
app.route('/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}`)
}
// Serve /assets/public folder at /assets route
const assetsPublicDir = path.resolve(__dirname, './assets/public');
if (fs.existsSync(assetsPublicDir)) {
app.use('/assets', express.static(assetsPublicDir));
console.log('Serving /assets from', assetsPublicDir);
} else {
console.warn('Assets public folder not found at', assetsPublicDir);
}
// Global error handler // Global error handler
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { app.onError((err, c) => {
console.error(err); console.error(err);
const status = err.statusCode || err.status || 500; // Handle different error types
const message = err.message || 'Internal Server Error'; let status = 500;
res.status(status).json({ message }); 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);
}); });
app.listen(4000, () => { serve({
console.log("Server is running on http://localhost:4000/api/mobile/"); fetch: app.fetch,
port: 4000,
}, (info) => {
console.log(`Server is running on http://localhost:${info.port}/api/mobile/`);
}); });

View file

@ -20,23 +20,22 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/client-s3": "^3.888.0",
"@aws-sdk/s3-request-presigner": "^3.888.0", "@aws-sdk/s3-request-presigner": "^3.888.0",
"@hono/node-server": "^1.19.11",
"@hono/trpc-server": "^0.4.2",
"@trpc/server": "^11.6.0", "@trpc/server": "^11.6.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.19",
"axios": "^1.11.0", "axios": "^1.11.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"expo-server-sdk": "^4.0.0", "expo-server-sdk": "^4.0.0",
"express": "^5.1.0",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hono": "^4.12.9",
"jose": "^6.2.2", "jose": "^6.2.2",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^24.5.2", "@types/node": "^24.5.2",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",

View file

@ -1,10 +1,10 @@
import { Router } from "express"; import { Hono } from 'hono';
import { authenticateStaff } from "@/src/middleware/staff-auth"; import { authenticateStaff } from "@/src/middleware/staff-auth";
const router = Router(); const router = new Hono();
// Apply staff authentication to all admin routes // Apply staff authentication to all admin routes
router.use(authenticateStaff); router.use('*', authenticateStaff);
const avRouter = router; const avRouter = router;

View file

@ -1,4 +1,4 @@
import { Request, Response } from "express"; import { Context } from 'hono';
import { import {
checkProductExistsByName, checkProductExistsByName,
checkUnitExists, checkUnitExists,
@ -25,8 +25,9 @@ type CreateDeal = {
/** /**
* Create a new product * Create a new product
*/ */
export const createProduct = async (req: Request, res: Response) => { export const createProduct = async (c: Context) => {
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals, tagIds } = req.body; const body = await c.req.parseBody({ all: true });
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals, tagIds } = body;
// Validate required fields // Validate required fields
if (!name || !unitId || !storeId || !price) { if (!name || !unitId || !storeId || !price) {
@ -34,100 +35,111 @@ export const createProduct = async (req: Request, res: Response) => {
} }
// Check for duplicate name // Check for duplicate name
const existingProduct = await checkProductExistsByName(name.trim()) const existingProduct = await checkProductExistsByName((name as string).trim())
if (existingProduct) { if (existingProduct) {
throw new ApiError("A product with this name already exists", 400); throw new ApiError("A product with this name already exists", 400);
} }
// Check if unit exists // Check if unit exists
const unitExists = await checkUnitExists(unitId) const unitExists = await checkUnitExists(parseInt(unitId as string))
if (!unitExists) { if (!unitExists) {
throw new ApiError("Invalid unit ID", 400); throw new ApiError("Invalid unit ID", 400);
} }
// Extract images from req.files // Extract images from body
const images = (req.files as Express.Multer.File[])?.filter(item => item.fieldname === 'images'); const images = body.images;
let uploadedImageUrls: string[] = []; let uploadedImageUrls: string[] = [];
if (images && Array.isArray(images)) { if (images) {
const imageUploadPromises = images.map((file, index) => { const imageFiles = Array.isArray(images) ? images : [images];
const imageUploadPromises = imageFiles.map((file, index) => {
if (file instanceof File) {
const key = `product-images/${Date.now()}-${index}`; const key = `product-images/${Date.now()}-${index}`;
return imageUploadS3(file.buffer, file.mimetype, key); return imageUploadS3(Buffer.from(file.stream() as any), file.type, key);
}); }
return null;
}).filter(Boolean);
uploadedImageUrls = await Promise.all(imageUploadPromises); uploadedImageUrls = await Promise.all(imageUploadPromises as Promise<string>[]);
} }
// Create product // Create product
const productData: any = { const productData: any = {
name, name: name as string,
shortDescription, shortDescription: shortDescription as string | undefined,
longDescription, longDescription: longDescription as string | undefined,
unitId, unitId: parseInt(unitId as string),
storeId, storeId: parseInt(storeId as string),
price, price: parseFloat(price as string),
marketPrice, marketPrice: marketPrice ? parseFloat(marketPrice as string) : null,
incrementStep: incrementStep || 1, incrementStep: incrementStep ? parseInt(incrementStep as string) : 1,
productQuantity: productQuantity || 1, productQuantity: productQuantity ? parseInt(productQuantity as string) : 1,
isSuspended: isSuspended || false, isSuspended: isSuspended === 'true',
isFlashAvailable: isFlashAvailable || false, isFlashAvailable: isFlashAvailable === 'true',
images: uploadedImageUrls, images: uploadedImageUrls,
}; };
if (flashPrice) { if (flashPrice) {
productData.flashPrice = parseFloat(flashPrice); productData.flashPrice = parseFloat(flashPrice as string);
} }
const newProduct = await createProductRecord(productData) const newProduct = await createProductRecord(productData)
// Handle deals if provided // Handle deals if provided
let createdDeals: AdminSpecialDeal[] = [] let createdDeals: AdminSpecialDeal[] = []
if (deals && Array.isArray(deals)) { if (deals) {
createdDeals = await createSpecialDealsForProduct(newProduct.id, deals) const parsedDeals = typeof deals === 'string' ? JSON.parse(deals) : deals;
if (Array.isArray(parsedDeals)) {
createdDeals = await createSpecialDealsForProduct(newProduct.id, parsedDeals)
}
} }
// Handle tag assignments if provided // Handle tag assignments if provided
if (tagIds && Array.isArray(tagIds)) { if (tagIds) {
await replaceProductTags(newProduct.id, tagIds) const parsedTagIds = typeof tagIds === 'string' ? JSON.parse(tagIds) : tagIds;
if (Array.isArray(parsedTagIds)) {
await replaceProductTags(newProduct.id, parsedTagIds)
}
} }
// Reinitialize stores to reflect changes // Reinitialize stores to reflect changes
scheduleStoreInitialization() scheduleStoreInitialization()
// Send response first // Send response first
res.status(201).json({ return c.json({
product: newProduct, product: newProduct,
deals: createdDeals, deals: createdDeals,
message: "Product created successfully", message: "Product created successfully",
}); }, 201);
}; };
/** /**
* Update a product * Update a product
*/ */
export const updateProduct = async (req: Request, res: Response) => { export const updateProduct = async (c: Context) => {
const id = req.params.id as string const id = c.req.param('id')
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = req.body; const body = await c.req.parseBody({ all: true });
const { name, shortDescription, longDescription, unitId, storeId, price, marketPrice, incrementStep, productQuantity, isSuspended, isFlashAvailable, flashPrice, deals:dealsRaw, imagesToDelete:imagesToDeleteRaw, tagIds } = body;
const deals = dealsRaw ? JSON.parse(dealsRaw) : null; const deals = dealsRaw ? (typeof dealsRaw === 'string' ? JSON.parse(dealsRaw) : dealsRaw) : null;
const imagesToDelete = imagesToDeleteRaw ? JSON.parse(imagesToDeleteRaw) : []; const imagesToDelete = imagesToDeleteRaw ? (typeof imagesToDeleteRaw === 'string' ? JSON.parse(imagesToDeleteRaw) : imagesToDeleteRaw) : [];
if (!name || !unitId || !storeId || !price) { if (!name || !unitId || !storeId || !price) {
throw new ApiError("Name, unitId, storeId, and price are required", 400); throw new ApiError("Name, unitId, storeId, and price are required", 400);
} }
// Check if unit exists // Check if unit exists
const unitExists = await checkUnitExists(unitId) const unitExists = await checkUnitExists(parseInt(unitId as string))
if (!unitExists) { if (!unitExists) {
throw new ApiError("Invalid unit ID", 400); throw new ApiError("Invalid unit ID", 400);
} }
// Get current product to handle image updates // Get current product to handle image updates
const currentImages = await getProductImagesById(parseInt(id)) const currentImages = await getProductImagesById(parseInt(id as string))
if (!currentImages) { if (!currentImages) {
throw new ApiError("Product not found", 404); throw new ApiError("Product not found", 404);
@ -154,45 +166,49 @@ export const updateProduct = async (req: Request, res: Response) => {
updatedImages = updatedImages.filter(img => !imagesToRemoveFromDb.includes(img)); updatedImages = updatedImages.filter(img => !imagesToRemoveFromDb.includes(img));
} }
// Extract new images from req.files // Extract new images from body
const images = (req.files as Express.Multer.File[])?.filter(item => item.fieldname === 'images'); const images = body.images;
let uploadedImageUrls: string[] = []; let uploadedImageUrls: string[] = [];
if (images && Array.isArray(images)) { if (images) {
const imageUploadPromises = images.map((file, index) => { const imageFiles = Array.isArray(images) ? images : [images];
const imageUploadPromises = imageFiles.map((file, index) => {
if (file instanceof File) {
const key = `product-images/${Date.now()}-${index}`; const key = `product-images/${Date.now()}-${index}`;
return imageUploadS3(file.buffer, file.mimetype, key); return imageUploadS3(Buffer.from(file.stream() as any), file.type, key);
}); }
return null;
}).filter(Boolean);
uploadedImageUrls = await Promise.all(imageUploadPromises); uploadedImageUrls = await Promise.all(imageUploadPromises as Promise<string>[]);
} }
// Combine remaining current images with new uploaded images // Combine remaining current images with new uploaded images
const finalImages = [...updatedImages, ...uploadedImageUrls]; const finalImages = [...updatedImages, ...uploadedImageUrls];
const updateData: any = { const updateData: any = {
name, name: name as string,
shortDescription, shortDescription: shortDescription as string | undefined,
longDescription, longDescription: longDescription as string | undefined,
unitId, unitId: parseInt(unitId as string),
storeId, storeId: parseInt(storeId as string),
price, price: parseFloat(price as string),
marketPrice, marketPrice: marketPrice ? parseFloat(marketPrice as string) : null,
incrementStep: incrementStep || 1, incrementStep: incrementStep ? parseInt(incrementStep as string) : 1,
productQuantity: productQuantity || 1, productQuantity: productQuantity ? parseInt(productQuantity as string) : 1,
isSuspended: isSuspended || false, isSuspended: isSuspended === 'true',
images: finalImages.length > 0 ? finalImages : undefined, images: finalImages.length > 0 ? finalImages : undefined,
}; };
if (isFlashAvailable !== undefined) { if (isFlashAvailable !== undefined) {
updateData.isFlashAvailable = isFlashAvailable; updateData.isFlashAvailable = isFlashAvailable === 'true';
} }
if (flashPrice !== undefined) { if (flashPrice !== undefined) {
updateData.flashPrice = flashPrice ? parseFloat(flashPrice) : null; updateData.flashPrice = flashPrice ? parseFloat(flashPrice as string) : null;
} }
const updatedProduct = await updateProductRecord(parseInt(id), updateData) const updatedProduct = await updateProductRecord(parseInt(id as string), updateData)
if (!updatedProduct) { if (!updatedProduct) {
throw new ApiError("Product not found", 404); throw new ApiError("Product not found", 404);
@ -200,22 +216,21 @@ export const updateProduct = async (req: Request, res: Response) => {
// Handle deals if provided // Handle deals if provided
if (deals && Array.isArray(deals)) { if (deals && Array.isArray(deals)) {
await updateProductDeals(parseInt(id), deals) await updateProductDeals(parseInt(id as string), deals)
} }
// Handle tag assignments if provided // Handle tag assignments if provided
// if (tagIds && Array.isArray(tagIds)) { if (tagIds) {
if (tagIds && Boolean(tagIds)) { const parsedTagIds = typeof tagIds === 'string' ? [parseInt(tagIds)] : (Array.isArray(tagIds) ? tagIds.map((t: any) => parseInt(t)) : [parseInt(tagIds as any)])
const tagIdsArray = Array.isArray(tagIds) ? tagIds : [+tagIds] await replaceProductTags(parseInt(id as string), parsedTagIds)
await replaceProductTags(parseInt(id), tagIdsArray)
} }
// Reinitialize stores to reflect changes // Reinitialize stores to reflect changes
scheduleStoreInitialization() scheduleStoreInitialization()
// Send response first // Send response first
res.status(200).json({ return c.json({
product: updatedProduct, product: updatedProduct,
message: "Product updated successfully", message: "Product updated successfully",
}); }, 200);
}; };

View file

@ -1,4 +1,4 @@
import { Request, Response } from "express"; import { Context } from 'hono';
import { scaffoldAssetUrl } from "@/src/lib/s3-client" import { scaffoldAssetUrl } from "@/src/lib/s3-client"
import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common" import { getNextDeliveryDate } from "@/src/trpc/apis/common-apis/common"
import { import {
@ -9,19 +9,19 @@ import {
/** /**
* Get all products summary for dropdown * Get all products summary for dropdown
*/ */
export const getAllProductsSummary = async (req: Request, res: Response) => { export const getAllProductsSummary = async (c: Context) => {
try { try {
const { tagId } = req.query; const tagId = c.req.query('tagId');
const tagIdNum = tagId ? parseInt(tagId as string) : undefined; const tagIdNum = tagId ? parseInt(tagId) : undefined;
// If tagId is provided but no products found, return empty array // If tagId is provided but no products found, return empty array
if (tagIdNum) { if (tagIdNum) {
const products = await getAllProductsWithUnits(tagIdNum); const products = await getAllProductsWithUnits(tagIdNum);
if (products.length === 0) { if (products.length === 0) {
return res.status(200).json({ return c.json({
products: [], products: [],
count: 0, count: 0,
}); }, 200);
} }
} }
@ -46,13 +46,13 @@ export const getAllProductsSummary = async (req: Request, res: Response) => {
}) })
); );
return res.status(200).json({ return c.json({
products: formattedProducts, products: formattedProducts,
count: formattedProducts.length, count: formattedProducts.length,
}); }, 200);
} catch (error) { } catch (error) {
console.error("Get products summary error:", error); console.error("Get products summary error:", error);
return res.status(500).json({ error: "Failed to fetch products summary" }); return c.json({ error: "Failed to fetch products summary" }, 500);
} }
}; };

View file

@ -1,7 +1,7 @@
import { Router } from "express"; import { Hono } from 'hono';
import { getAllProductsSummary } from "@/src/apis/common-apis/apis/common-product.controller" import { getAllProductsSummary } from "@/src/apis/common-apis/apis/common-product.controller"
const router = Router(); const router = new Hono();
router.get("/summary", getAllProductsSummary); router.get("/summary", getAllProductsSummary);

View file

@ -1,9 +1,9 @@
import { Router } from "express"; import { Hono } from 'hono';
import commonProductsRouter from "@/src/apis/common-apis/apis/common-product.router" import commonProductsRouter from "@/src/apis/common-apis/apis/common-product.router"
const router = Router(); const router = new Hono();
router.use('/products', commonProductsRouter) router.route('/products', commonProductsRouter)
const commonRouter = router; const commonRouter = router;

View file

@ -1,6 +1,11 @@
import express from 'express'; // catchAsync is no longer needed with Hono
const catchAsync = // Hono handles async errors automatically
(fn: express.RequestHandler) => // This file is kept for backward compatibility but should be removed in the future
(req: express.Request, res: express.Response, next: express.NextFunction) =>
Promise.resolve(fn(req, res, next)).catch(next); import { Context } from 'hono';
const catchAsync = (fn: (c: Context) => Promise<Response>) => {
return fn;
};
export default catchAsync; export default catchAsync;

View file

@ -1,56 +1,36 @@
import { Router, Request, Response, NextFunction } from "express"; import { Hono } from 'hono';
import avRouter from "@/src/apis/admin-apis/apis/av-router" import avRouter from "@/src/apis/admin-apis/apis/av-router"
import { ApiError } from "@/src/lib/api-error" import { ApiError } from "@/src/lib/api-error"
import v1Router from "@/src/v1-router" import v1Router from "@/src/v1-router"
import testController from "@/src/test-controller" import testController from "@/src/test-controller"
import { authenticateUser } from "@/src/middleware/auth.middleware" import { authenticateUser } from "@/src/middleware/auth.middleware"
const router = new Hono();
const router = Router();
// Health check endpoints (no auth required) // Health check endpoints (no auth required)
router.get('/health', (req: Request, res: Response) => { router.get('/health', (c) => {
res.status(200).json({ return c.json({
status: 'OK', status: 'OK',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
uptime: process.uptime(), uptime: process.uptime(),
message: 'Hello world' message: 'Hello world'
}); });
}); });
router.get('/seed', (req:Request, res: Response) => {
res.status(200).json({ router.get('/seed', (c) => {
return c.json({
status: 'OK', status: 'OK',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
uptime: process.uptime() uptime: process.uptime()
}); });
}) });
// Apply authentication middleware to all subsequent routes // Apply authentication middleware to all subsequent routes
router.use(authenticateUser); router.use('*', authenticateUser);
router.use('/v1', v1Router); router.route('/v1', v1Router);
// router.use('/av', avRouter); // router.route('/av', avRouter);
router.use('/test', testController); router.route('/test', testController);
// Global error handling middleware
router.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error('Error:', err);
if (err instanceof ApiError) {
return res.status(err.statusCode).json({
error: err.message,
details: err.details,
statusCode: err.statusCode
});
}
// Handle unknown errors
return res.status(500).json({
error: 'Internal Server Error',
message: process.env.NODE_ENV === 'development' ? err.message : undefined,
statusCode: 500
});
});
const mainRouter = router; const mainRouter = router;

View file

@ -1,32 +1,31 @@
import { Request, Response, NextFunction } from 'express'; import { Context, Next } from 'hono';
import { jwtVerify } from 'jose'; import { jwtVerify } from 'jose';
import { getStaffUserById, isUserSuspended } from '@/src/dbService'; import { getStaffUserById, isUserSuspended } from '@/src/dbService';
import { ApiError } from '@/src/lib/api-error'; import { ApiError } from '@/src/lib/api-error';
import { encodedJwtSecret } from '@/src/lib/env-exporter'; import { encodedJwtSecret } from '@/src/lib/env-exporter';
interface AuthenticatedRequest extends Request { interface UserContext {
user?: {
userId: number; userId: number;
name?: string; name?: string;
email?: string; email?: string;
mobile?: string; mobile?: string;
};
staffUser?: {
id: number;
name: string;
};
} }
export const authenticateUser = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => { interface StaffContext {
id: number;
name: string;
}
export const authenticateUser = async (c: Context, next: Next) => {
try { try {
const authHeader = req.headers.authorization; const authHeader = c.req.header('authorization');
if (!authHeader?.startsWith('Bearer ')) { if (!authHeader?.startsWith('Bearer ')) {
throw new ApiError('Authorization token required', 401); throw new ApiError('Authorization token required', 401);
} }
const token = authHeader.substring(7); const token = authHeader.substring(7);
console.log(req.headers) console.log(c.req.header)
const { payload } = await jwtVerify(token, encodedJwtSecret); const { payload } = await jwtVerify(token, encodedJwtSecret);
const decoded = payload as any; const decoded = payload as any;
@ -51,13 +50,13 @@ export const authenticateUser = async (req: AuthenticatedRequest, res: Response,
throw new ApiError('Invalid staff token', 401); throw new ApiError('Invalid staff token', 401);
} }
req.staffUser = { c.set('staffUser', {
id: staff.id, id: staff.id,
name: staff.name, name: staff.name,
}; });
} else { } else {
// This is a regular user token // This is a regular user token
req.user = decoded; c.set('user', decoded);
/* /*
// Old implementation - direct DB queries: // Old implementation - direct DB queries:
@ -82,8 +81,8 @@ export const authenticateUser = async (req: AuthenticatedRequest, res: Response,
} }
} }
next(); await next();
} catch (error) { } catch (error) {
next(error); throw error;
} }
}; };

View file

@ -1,21 +1,12 @@
import { Request, Response, NextFunction } from 'express'; import { Context, Next } from 'hono';
import { jwtVerify, errors } from 'jose'; import { jwtVerify, errors } from 'jose';
import { ApiError } from '@/src/lib/api-error' import { ApiError } from '@/src/lib/api-error'
import { encodedJwtSecret } from '@/src/lib/env-exporter'; import { encodedJwtSecret } from '@/src/lib/env-exporter';
// Extend the Request interface to include user property export const verifyToken = async (c: Context, next: Next) => {
declare global {
namespace Express {
interface Request {
user?: any;
}
}
}
export const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
try { try {
// Get token from Authorization header // Get token from Authorization header
const authHeader = req.headers.authorization; const authHeader = c.req.header('authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith('Bearer ')) {
@ -32,37 +23,38 @@ export const verifyToken = async (req: Request, res: Response, next: NextFunctio
const { payload } = await jwtVerify(token, encodedJwtSecret); const { payload } = await jwtVerify(token, encodedJwtSecret);
// Add user info to request // Add user info to context
req.user = payload; c.set('user', payload);
next(); await next();
} catch (error) { } catch (error) {
if (error instanceof errors.JOSEError) { if (error instanceof errors.JOSEError) {
next(new ApiError('Invalid Auth Credentials', 401)); throw new ApiError('Invalid Auth Credentials', 401);
} else { } else {
next(error); throw error;
} }
} }
}; };
export const requireRole = (roles: string[]) => { export const requireRole = (roles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => { return async (c: Context, next: Next) => {
try { try {
if (!req.user) { const user = c.get('user');
if (!user) {
throw new ApiError('Authentication required', 401); throw new ApiError('Authentication required', 401);
} }
// Check if user has any of the required roles // Check if user has any of the required roles
const userRoles = req.user.roles || []; const userRoles = user.roles || [];
const hasPermission = roles.some(role => userRoles.includes(role)); const hasPermission = roles.some(role => userRoles.includes(role));
if (!hasPermission) { if (!hasPermission) {
throw new ApiError('Access denied. Insufficient permissions', 403); throw new ApiError('Access denied. Insufficient permissions', 403);
} }
next(); await next();
} catch (error) { } catch (error) {
next(error); throw error;
} }
}; };
}; };

View file

@ -1,21 +1,9 @@
import { Request, Response, NextFunction } from 'express'; import { Context, Next } from 'hono';
import { jwtVerify, errors } from 'jose'; import { jwtVerify } from 'jose';
import { getStaffUserById } from '@/src/dbService'; import { getStaffUserById } from '@/src/dbService';
import { ApiError } from '@/src/lib/api-error'; import { ApiError } from '@/src/lib/api-error';
import { encodedJwtSecret } from '@/src/lib/env-exporter'; import { encodedJwtSecret } from '@/src/lib/env-exporter';
// Extend Request interface to include staffUser
declare global {
namespace Express {
interface Request {
staffUser?: {
id: number;
name: string;
};
}
}
}
/** /**
* Verify JWT token and extract payload * Verify JWT token and extract payload
*/ */
@ -29,12 +17,12 @@ const verifyStaffToken = async (token: string) => {
}; };
/** /**
* Middleware to authenticate staff users and attach staffUser to request * Middleware to authenticate staff users and attach staffUser to context
*/ */
export const authenticateStaff = async (req: Request, res: Response, next: NextFunction) => { export const authenticateStaff = async (c: Context, next: Next) => {
try { try {
// Extract token from Authorization header // Extract token from Authorization header
const authHeader = req.headers.authorization; const authHeader = c.req.header('authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new ApiError('Staff authentication required', 401); throw new ApiError('Staff authentication required', 401);
@ -72,14 +60,14 @@ export const authenticateStaff = async (req: Request, res: Response, next: NextF
throw new ApiError('Staff user not found', 401); throw new ApiError('Staff user not found', 401);
} }
// Attach staff user to request // Attach staff user to context
req.staffUser = { c.set('staffUser', {
id: staff.id, id: staff.id,
name: staff.name, name: staff.name,
}; });
next(); await next();
} catch (error) { } catch (error) {
next(error); throw error;
} }
}; };

View file

@ -1,9 +1,9 @@
import { Router, Request, Response } from 'express'; import { Hono } from 'hono';
const router = Router(); const router = new Hono();
router.get('/', (req: Request, res: Response) => { router.get('/', (c) => {
res.json({ return c.json({
status: 'ok', status: 'ok',
message: 'Health check passed', message: 'Health check passed',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),

View file

@ -357,7 +357,8 @@ export const authRouter = router({
? await generateSignedUrlFromS3Url(userDetail.profileImage) ? await generateSignedUrlFromS3Url(userDetail.profileImage)
: null : null
const token = ctx.req.headers.authorization?.replace('Bearer ', '') || '' const authHeader = ctx.req.header('authorization');
const token = authHeader?.replace('Bearer ', '') || ''
const response: UserAuthResponse = { const response: UserAuthResponse = {
token, token,

View file

@ -1,9 +1,8 @@
import { initTRPC, TRPCError } from '@trpc/server'; import { initTRPC, TRPCError } from '@trpc/server';
import { type CreateExpressContextOptions } from '@trpc/server/adapters/express'; import type { Context as HonoContext } from 'hono';
export interface Context { export interface Context {
req: CreateExpressContextOptions['req']; req: HonoContext['req'];
res: CreateExpressContextOptions['res'];
user?: any; user?: any;
staffUser?: { staffUser?: {
id: number; id: number;

13
apps/backend/src/types/hono.d.ts vendored Normal file
View file

@ -0,0 +1,13 @@
import { Context as HonoContext } from 'hono';
declare module 'hono' {
interface ContextVariableMap {
user?: any;
staffUser?: {
id: number;
name: string;
};
}
}
export {};

View file

@ -1,12 +1,11 @@
import { Router } from "express"; import { Hono } from 'hono';
import avRouter from "@/src/apis/admin-apis/apis/av-router" import avRouter from "@/src/apis/admin-apis/apis/av-router"
import commonRouter from "@/src/apis/common-apis/apis/common.router" import commonRouter from "@/src/apis/common-apis/apis/common.router"
const router = Router(); const router = new Hono();
router.use('/av', avRouter);
router.use('/cm', commonRouter);
router.route('/av', avRouter);
router.route('/cm', commonRouter);
const v1Router = router; const v1Router = router;

415
package-lock.json generated
View file

@ -127,23 +127,22 @@
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.888.0", "@aws-sdk/client-s3": "^3.888.0",
"@aws-sdk/s3-request-presigner": "^3.888.0", "@aws-sdk/s3-request-presigner": "^3.888.0",
"@hono/node-server": "^1.19.11",
"@hono/trpc-server": "^0.4.2",
"@trpc/server": "^11.6.0", "@trpc/server": "^11.6.0",
"@turf/turf": "^7.2.0", "@turf/turf": "^7.2.0",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.19",
"axios": "^1.11.0", "axios": "^1.11.0",
"bcryptjs": "^3.0.2", "bcryptjs": "^3.0.2",
"cors": "^2.8.5",
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"dotenv": "^17.2.1", "dotenv": "^17.2.1",
"expo-server-sdk": "^4.0.0", "expo-server-sdk": "^4.0.0",
"express": "^5.1.0",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"hono": "^4.12.9",
"jose": "^6.2.2", "jose": "^6.2.2",
"zod": "^4.1.12" "zod": "^4.1.12"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^5.0.3",
"@types/node": "^24.5.2", "@types/node": "^24.5.2",
"rimraf": "^6.1.2", "rimraf": "^6.1.2",
"ts-node-dev": "^2.0.0", "ts-node-dev": "^2.0.0",
@ -3720,6 +3719,31 @@
"excpretty": "build/cli.js" "excpretty": "build/cli.js"
} }
}, },
"node_modules/@hono/node-server": {
"version": "1.19.11",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"peerDependencies": {
"hono": "^4"
}
},
"node_modules/@hono/trpc-server": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/@hono/trpc-server/-/trpc-server-0.4.2.tgz",
"integrity": "sha512-3TDrc42CZLgcTFkXQba+y7JlRWRiyw1AqhLqztWyNS2IFT+3bHld0lxKdGBttCtGKHYx0505dM67RMazjhdZqw==",
"license": "MIT",
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"@trpc/server": "^10.10.0 || >11.0.0-rc",
"hono": ">=4.0.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"dev": true, "dev": true,
@ -7600,35 +7624,11 @@
"version": "2.4.6", "version": "2.4.6",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/body-parser": {
"version": "1.19.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
}
},
"node_modules/@types/canvas-confetti": { "node_modules/@types/canvas-confetti": {
"version": "1.9.0", "version": "1.9.0",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/connect": {
"version": "3.4.38",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/cors": {
"version": "2.8.19",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/d3-voronoi": { "node_modules/@types/d3-voronoi": {
"version": "1.1.12", "version": "1.1.12",
"license": "MIT" "license": "MIT"
@ -7642,27 +7642,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/express": {
"version": "5.0.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
"@types/serve-static": "^2"
}
},
"node_modules/@types/express-serve-static-core": {
"version": "5.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
"@types/range-parser": "*",
"@types/send": "*"
}
},
"node_modules/@types/geojson": { "node_modules/@types/geojson": {
"version": "7946.0.16", "version": "7946.0.16",
"license": "MIT" "license": "MIT"
@ -7706,11 +7685,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/http-errors": {
"version": "2.0.5",
"dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6", "version": "2.0.6",
"license": "MIT" "license": "MIT"
@ -7781,16 +7755,6 @@
"pg-types": "^2.2.0" "pg-types": "^2.2.0"
} }
}, },
"node_modules/@types/qs": {
"version": "6.15.0",
"dev": true,
"license": "MIT"
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": { "node_modules/@types/react": {
"version": "19.0.14", "version": "19.0.14",
"license": "MIT", "license": "MIT",
@ -7821,23 +7785,6 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/send": {
"version": "1.2.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "2.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*"
}
},
"node_modules/@types/stack-utils": { "node_modules/@types/stack-utils": {
"version": "2.0.3", "version": "2.0.3",
"license": "MIT" "license": "MIT"
@ -8232,17 +8179,6 @@
"node": ">=6.5" "node": ">=6.5"
} }
}, },
"node_modules/accepts": {
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.16.0", "version": "8.16.0",
"license": "MIT", "license": "MIT",
@ -8915,28 +8851,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/body-parser": {
"version": "2.2.2",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/boolbase": { "node_modules/boolbase": {
"version": "1.0.0", "version": "1.0.0",
"license": "ISC" "license": "ISC"
@ -9573,17 +9487,6 @@
"@babel/types": "^7.6.1" "@babel/types": "^7.6.1"
} }
}, },
"node_modules/content-disposition": {
"version": "1.0.1",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/content-type": { "node_modules/content-type": {
"version": "1.0.5", "version": "1.0.5",
"license": "MIT", "license": "MIT",
@ -9606,13 +9509,6 @@
"version": "2.0.0", "version": "2.0.0",
"license": "MIT" "license": "MIT"
}, },
"node_modules/cookie-signature": {
"version": "1.2.2",
"license": "MIT",
"engines": {
"node": ">=6.6.0"
}
},
"node_modules/copy-anything": { "node_modules/copy-anything": {
"version": "4.0.5", "version": "4.0.5",
"license": "MIT", "license": "MIT",
@ -9637,21 +9533,6 @@
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
} }
}, },
"node_modules/cors": {
"version": "2.8.6",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "5.2.1", "version": "5.2.1",
"license": "MIT", "license": "MIT",
@ -11587,47 +11468,6 @@
"version": "3.1.3", "version": "3.1.3",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/express": {
"version": "5.2.1",
"license": "MIT",
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
"content-disposition": "^1.0.0",
"content-type": "^1.0.5",
"cookie": "^0.7.1",
"cookie-signature": "^1.2.1",
"debug": "^4.4.0",
"depd": "^2.0.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"finalhandler": "^2.1.0",
"fresh": "^2.0.0",
"http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"mime-types": "^3.0.0",
"on-finished": "^2.4.1",
"once": "^1.4.0",
"parseurl": "^1.3.3",
"proxy-addr": "^2.0.7",
"qs": "^6.14.0",
"range-parser": "^1.2.1",
"router": "^2.2.0",
"send": "^1.1.0",
"serve-static": "^2.2.0",
"statuses": "^2.0.1",
"type-is": "^2.0.1",
"vary": "^1.1.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/fallback-ui": { "node_modules/fallback-ui": {
"resolved": "apps/fallback-ui", "resolved": "apps/fallback-ui",
"link": true "link": true
@ -11809,25 +11649,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/finalhandler": {
"version": "2.1.1",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
"statuses": "^2.0.1"
},
"engines": {
"node": ">= 18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"license": "MIT", "license": "MIT",
@ -11996,13 +11817,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/fresh": {
"version": "2.0.0",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"license": "ISC" "license": "ISC"
@ -12439,6 +12253,15 @@
"version": "16.13.1", "version": "16.13.1",
"license": "MIT" "license": "MIT"
}, },
"node_modules/hono": {
"version": "4.12.9",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
"integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"node_modules/hosted-git-info": { "node_modules/hosted-git-info": {
"version": "7.0.2", "version": "7.0.2",
"license": "ISC", "license": "ISC",
@ -12486,20 +12309,6 @@
"version": "1.1.0", "version": "1.1.0",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/iconv-lite": {
"version": "0.7.2",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/ieee754": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
"funding": [ "funding": [
@ -12940,10 +12749,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-promise": {
"version": "4.0.0",
"license": "MIT"
},
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"license": "MIT", "license": "MIT",
@ -13773,27 +13578,10 @@
"version": "2.0.14", "version": "2.0.14",
"license": "CC0-1.0" "license": "CC0-1.0"
}, },
"node_modules/media-typer": {
"version": "1.1.0",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/memoize-one": { "node_modules/memoize-one": {
"version": "5.2.1", "version": "5.2.1",
"license": "MIT" "license": "MIT"
}, },
"node_modules/merge-descriptors": {
"version": "2.0.0",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"license": "MIT" "license": "MIT"
@ -14175,27 +13963,6 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/mime-db": {
"version": "1.54.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "3.0.2",
"license": "MIT",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/mimic-fn": { "node_modules/mimic-fn": {
"version": "1.2.0", "version": "1.2.0",
"license": "MIT", "license": "MIT",
@ -14311,13 +14078,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/negotiator": {
"version": "1.0.0",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/nested-error-stacks": { "node_modules/nested-error-stacks": {
"version": "2.0.1", "version": "2.0.1",
"license": "MIT" "license": "MIT"
@ -15443,19 +15203,6 @@
"qrcode-terminal": "bin/qrcode-terminal.js" "qrcode-terminal": "bin/qrcode-terminal.js"
} }
}, },
"node_modules/qs": {
"version": "6.15.0",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/query-string": { "node_modules/query-string": {
"version": "7.1.3", "version": "7.1.3",
"license": "MIT", "license": "MIT",
@ -15516,19 +15263,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/raw-body": {
"version": "3.0.2",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/rbush": { "node_modules/rbush": {
"version": "3.0.1", "version": "3.0.1",
"license": "MIT", "license": "MIT",
@ -16394,28 +16128,6 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/router": {
"version": "2.2.0",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/router/node_modules/path-to-regexp": {
"version": "8.3.0",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"funding": [ "funding": [
@ -16529,30 +16241,6 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/send": {
"version": "1.2.1",
"license": "MIT",
"dependencies": {
"debug": "^4.4.3",
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"etag": "^1.8.1",
"fresh": "^2.0.0",
"http-errors": "^2.0.1",
"mime-types": "^3.0.2",
"ms": "^2.1.3",
"on-finished": "^2.4.1",
"range-parser": "^1.2.1",
"statuses": "^2.0.2"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/serialize-error": { "node_modules/serialize-error": {
"version": "2.1.0", "version": "2.1.0",
"license": "MIT", "license": "MIT",
@ -16577,23 +16265,6 @@
"seroval": "^1.0" "seroval": "^1.0"
} }
}, },
"node_modules/serve-static": {
"version": "2.2.1",
"license": "MIT",
"dependencies": {
"encodeurl": "^2.0.0",
"escape-html": "^1.0.3",
"parseurl": "^1.3.3",
"send": "^1.2.0"
},
"engines": {
"node": ">= 18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/server-only": { "node_modules/server-only": {
"version": "0.0.1", "version": "0.0.1",
"license": "MIT" "license": "MIT"
@ -17944,18 +17615,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/type-is": {
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typed-array-buffer": { "node_modules/typed-array-buffer": {
"version": "1.0.3", "version": "1.0.3",
"dev": true, "dev": true,