From 10baaf941c613f39012e98a4947a0c1df28dc265 Mon Sep 17 00:00:00 2001 From: oboscole Date: Tue, 29 Jul 2025 19:40:11 +0100 Subject: [PATCH 1/7] fix --- src/controllers/facility.controller.ts | 142 ++++-- src/services/db/facility.service.ts | 600 +++++++++++++++++++++++-- src/types/types.ts | 11 + 3 files changed, 671 insertions(+), 82 deletions(-) diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index 4cc035c..ba070ed 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -17,7 +17,9 @@ import { getAllFacility_ByFiltering, getFacilitiesByOperator, updateFacilityCapacity, - searchFacilities, + searchEverythingGlobally, + searchFacilitiesWithFilters, + } from "../services/db/facility.service"; import { StatusCodes } from "http-status-codes"; import { @@ -25,9 +27,9 @@ import { NotFoundError, UnauthorizedError, } from "../errors/errors"; -import { PrismaClient } from "@prisma/client"; +import { FacilityType, PrismaClient } from "@prisma/client"; import { deleteImageFromCloudinary } from "../services/cloudinary.service"; -import { FacilityFilterOptions } from "../types/types"; +import { FacilityFilterOptions, FacilitySearchFilters } from "../types/types"; const prisma = new PrismaClient(); export const addFacility = async ( @@ -267,53 +269,125 @@ export const updateCapacity = async ( } }; + +// export const globalFacilitySearch = async ( +// req: Request, +// res: Response, +// next: NextFunction +// ) => { +// try { +// const { term = "", page = 1, limit = 10 } = req.query; +// const userId = req.currentUser?.id; +// const userRole = req.currentUser?.role; + +// if (!userId) { +// res.status(StatusCodes.UNAUTHORIZED).json({ +// message: "Authentication required" +// }); +// return +// } + +// const results = await searchEverythingGlobally( +// term as string, +// userId, +// userRole, +// Number(page), +// Number(limit) +// ); + +// res.status(StatusCodes.OK).json({ +// message: "Search results fetched successfully", +// data: results, +// }); + +// } catch (error) { +// next(error); +// } +// }; + + + +// UPDATED CONTROLLER + + export const globalFacilitySearch = async ( req: Request, res: Response, next: NextFunction ) => { try { - const { + const { + term = "", + page = 1, + limit = 10, location, type, available, - operatorName, minPrice, maxPrice, - page = "1", - limit = "10", + minCapacity, + maxCapacity } = req.query; + + const userId = req.currentUser?.id; + const userRole = req.currentUser?.role; + + if (!userId) { + res.status(StatusCodes.FORBIDDEN).json({ + message: "Authentication required" + }); + return; + } - const allowedTypes = [ - "DRYER", - "STORAGE", - "PROCESSING", - "COLDROOM", - "OTHER", - ] as const; - type FacilityType = (typeof allowedTypes)[number]; - - const filters: FacilityFilterOptions = { - location: location as string, - type: allowedTypes.includes(type as FacilityType) - ? (type as FacilityType) - : undefined, - available: - available === "true" ? true : available === "false" ? false : undefined, - operatorName: operatorName as string, - minPrice: minPrice ? Number(minPrice) : undefined, - maxPrice: maxPrice ? Number(maxPrice) : undefined, - page: Number(page), - limit: Number(limit), - }; - - const response = await searchFacilities(filters); + // Check if filters are provided + const hasFilters = location || type || available !== undefined || + minPrice || maxPrice || minCapacity || maxCapacity; + + let results; + + if (hasFilters) { + // Use filtered search + const filters: FacilitySearchFilters = { + ...(location && { location: location as string }), + ...(type && { type: type as FacilityType }), + ...(available !== undefined && { available: available === 'true' }), + ...(minPrice && { minPrice: Number(minPrice) }), + ...(maxPrice && { maxPrice: Number(maxPrice) }), + ...(minCapacity && { minCapacity: Number(minCapacity) }), + ...(maxCapacity && { maxCapacity: Number(maxCapacity) }) + }; + + results = await searchFacilitiesWithFilters( + term as string, + filters, + userId, + userRole, + Number(page), + Number(limit) + ); + } else { + // Use global search + results = await searchEverythingGlobally( + term as string, + userId, + userRole, + Number(page), + Number(limit) + ); + } - res.status(200).json({ - message: "Facilities fetched successfully", - data: response, + res.status(StatusCodes.OK).json({ + message: "Search results fetched successfully", + data: results, + searchTerm: term, + userRole, + hasFilters }); + } catch (error) { + console.error('Controller error:', error); next(error); } }; + + diff --git a/src/services/db/facility.service.ts b/src/services/db/facility.service.ts index b3d12aa..8b22248 100644 --- a/src/services/db/facility.service.ts +++ b/src/services/db/facility.service.ts @@ -1,12 +1,15 @@ -import { Prisma } from "@prisma/client"; +import { FacilityType, Prisma, booking_status } from "@prisma/client"; import { prisma } from "../../config/config.db"; import { BadRequestError, NotFoundError } from "../../errors/errors"; import { FacilityFilterOptions, + FacilitySearchFilters, FacilityUpdateData, GetByOperatorOptions, UserRole, } from "../../types/types"; +import { Request, Response, NextFunction } from "express"; +import { StatusCodes } from "http-status-codes"; export const createFacility = async (data: Prisma.FacilityCreateInput) => { try { @@ -210,59 +213,560 @@ export const updateFacilityCapacity = async ( return updatedFacility; }; -export const searchFacilities = async (filters: FacilityFilterOptions) => { - try { - const { - location, - type, - available, - operatorName, - minPrice, - maxPrice, - page = 1, - limit = 10, - } = filters; - const search: any = { - ...(location && { - location: { contains: location, mode: "insensitive" }, - }), - ...(type && { type }), - ...(available !== undefined && { available }), - ...(minPrice && - maxPrice && { pricePerDay: { gte: minPrice, lte: maxPrice } }), - ...(minPrice && !maxPrice && { pricePerDay: { gte: minPrice } }), - ...(!minPrice && maxPrice && { pricePerDay: { lte: maxPrice } }), + +export const searchEverythingGlobally = async ( + term: string, + userId: string, + userRole: string, + page: number = 1, + limit: number = 10 +) => { + + const skip = (page - 1) * limit; + const numericTerm = !isNaN(Number(term)) ? Number(term) : null; + + if (!term.trim() || /^[@#\$%\^&\*\(\)_\+\-=\[\]\{\}\|;':\",./<>\?~`!]+$/.test(term)) { + return { + farmers: [], + operators: [], + facilities: [], + bookings: [], + transactions: [], + notifications: [], }; + } - const facilities = await prisma.facility.findMany({ - where: { - ...search, - ...(operatorName && { - operator: { + try { + const [farmers, operators, facilities, bookings, transactions, notifications] = await Promise.all([ + + // FARMERS SEARCH (Admin only) + userRole === 'ADMIN' ? (async () => { + console.log('🔍 Searching farmers (admin access)...'); + + const result = await prisma.farmer.findMany({ + where: { OR: [ - { firstName: { contains: operatorName, mode: "insensitive" } }, - { lastName: { contains: operatorName, mode: "insensitive" } }, - { businessName: { contains: operatorName, mode: "insensitive" } }, - ], + { firstName: { contains: term, mode: "insensitive" } }, + { lastName: { contains: term, mode: "insensitive" } }, + { phone: { contains: term, mode: "insensitive" } }, + { address: { contains: term, mode: "insensitive" } }, + ...(numericTerm ? [{ id: numericTerm }] : []) + ] }, - }), - }, - include: { - operator: true, - }, - skip: (Number(page) - 1) * Number(limit), - take: Number(limit), - orderBy: { - createdAt: "desc", - }, - }); + skip, + take: limit, + orderBy: [ + { firstName: 'asc' }, + { lastName: 'asc' } + ] + }); + + console.log(`Found ${result.length} farmers`); + return result; + })() : [], + + // OPERATORS SEARCH (Admin only) + userRole === 'ADMIN' ? (async () => { + console.log('🔍 Searching operators (admin access)...'); + + const result = await prisma.operator.findMany({ + where: { + OR: [ + { firstName: { contains: term, mode: "insensitive" } }, + { lastName: { contains: term, mode: "insensitive" } }, + { phone: { contains: term, mode: "insensitive" } }, + { businessName: { contains: term, mode: "insensitive" } }, + { address: { contains: term, mode: "insensitive" } }, + ...(numericTerm ? [{ id: numericTerm }] : []) + ] + }, + skip, + take: limit, + orderBy: [ + { businessName: 'asc' }, + { firstName: 'asc' } + ] + }); + + console.log(`Found ${result.length} operators`); + return result; + })() : [], + + // FACILITIES SEARCH - FIXED: Using prisma.facility instead of prisma.booking + (async () => { + console.log('🔍 Facilities search - GLOBAL for farmers...'); + + // Build facility search conditions + const facilitySearchConditions: any[] = [ + { name: { contains: term, mode: "insensitive" } }, + { description: { contains: term, mode: "insensitive" } }, + { location: { contains: term, mode: "insensitive" } } + ]; + + // Enhanced facility type matching + const facilityTypeMapping = { + 'STORAGE': ['store', 'storage', 'warehouse', 'depot'], + 'DRYER': ['dry', 'dryer', 'drying', 'dehydrate'], + 'PROCESSING': ['process', 'processing', 'manufacture', 'production'], + 'OTHER': ['other', 'misc', 'miscellaneous'] + }; + + // Check if search term matches any facility type + Object.entries(facilityTypeMapping).forEach(([type, keywords]) => { + keywords.forEach(keyword => { + if (term.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(term.toLowerCase())) { + facilitySearchConditions.push({ type: { equals: type as FacilityType } }); + } + }); + }); + + const statusKeywordMap = { + 'RESERVED': ['reserved', 'pending', 'wait', 'waiting', 'review'], + 'CONFIRMED': ['confirmed', 'approved', 'accept', 'accepted'], + 'CANCELLED': ['cancelled', 'canceled', 'reject', 'rejected'], + 'COMPLETED': ['completed', 'done', 'finished'], +}; + +Object.entries(statusKeywordMap).forEach(([status, keywords]) => { + keywords.forEach(keyword => { + if ( + term.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(term.toLowerCase()) + ) { + facilitySearchConditions.push({ + bookings: { + some: { + status: status + } + } + }); + } + }); +}); + // Direct facility type match + const upperTerm = term.toUpperCase(); + if (Object.values(FacilityType).includes(upperTerm as FacilityType)) { + facilitySearchConditions.push({ type: { equals: upperTerm as FacilityType } }); + } - return facilities; + // Numeric price search + if (numericTerm !== null) { + facilitySearchConditions.push( + { pricePerDay: { equals: numericTerm } }, + { pricePerDay: { lte: numericTerm * 1.1, gte: numericTerm * 0.9 } } // ±10% range + ); + } + + // Search in operator details + facilitySearchConditions.push( + { operator: { firstName: { contains: term, mode: "insensitive" } } }, + { operator: { lastName: { contains: term, mode: "insensitive" } } }, + { operator: { businessName: { contains: term, mode: "insensitive" } } } + ); + + let whereClause: any = {}; + + if (userRole === 'OPERATOR') { + console.log('🔒 Operator: Filtering facilities by operator user_id:', userId); + whereClause = { + AND: [ + { operator: { user_id: userId } }, + { OR: facilitySearchConditions } + ] + }; + } else { + // FARMER and ADMIN can see ALL facilities + console.log('✅ Global facility search - showing ALL facilities'); + whereClause = { OR: facilitySearchConditions }; + } + + const result = await prisma.facility.findMany({ + where: whereClause, + include: { + operator: { + select: { + id: true, + firstName: true, + lastName: true, + phone: true, + businessName: true, + address: true, + createdAt: true, + updatedAt: true, + user_id: true + } + } + }, + skip, + take: limit, + orderBy: [ + { available: 'desc' }, // Available facilities first + { name: 'asc' } + ] + }); + + console.log(`Found ${result.length} facilities`); + return result; + })(), + + // BOOKINGS SEARCH - FIXED: Proper enum values + (async () => { + console.log('🔍 Bookings search...'); + + // Build booking search conditions + const bookingSearchConditions: any[] = [ + { facility: { name: { contains: term, mode: "insensitive" } } }, + { facility: { location: { contains: term, mode: "insensitive" } } }, + { farmer: { firstName: { contains: term, mode: "insensitive" } } }, + { farmer: { lastName: { contains: term, mode: "insensitive" } } }, + { farmer: { phone: { contains: term, mode: "insensitive" } } } + ]; + + // FIXED: Booking status search with correct enum values + const statusMapping = { + 'RESERVED': ['reserved', 'pending', 'wait', 'waiting', 'review'], + 'CONFIRMED': ['confirmed', 'approved', 'accept', 'accepted'], + 'CANCELLED': ['cancelled', 'canceled', 'reject', 'rejected'], + }; + + Object.entries(statusMapping).forEach(([status, keywords]) => { + keywords.forEach(keyword => { + if (term.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(term.toLowerCase())) { + if (term.toLowerCase() === keyword.toLowerCase()) { + bookingSearchConditions.push({ status: { equals: status } }); + } + } + }); + }); + + // Direct status match - FIXED: Check against correct enum + const upperTerm = term.toUpperCase(); + const validStatuses = ['RESERVED', 'CONFIRMED', 'CANCELLED', 'COMPLETED']; + if (validStatuses.includes(upperTerm)) { + bookingSearchConditions.push({ status: { equals: upperTerm } }); + } + + // Numeric amount search + if (numericTerm !== null) { + bookingSearchConditions.push( + { amount: { equals: numericTerm } }, + { amount: { lte: numericTerm * 1.1, gte: numericTerm * 0.9 } } + ); + } + + let whereClause: any = {}; + + if (userRole === 'FARMER') { + console.log('🔒 Filtering bookings by farmer user_id:', userId); + whereClause = { + AND: [ + { farmer: { user_id: userId } }, + { OR: bookingSearchConditions } + ] + }; + } else if (userRole === 'OPERATOR') { + console.log('🔒 Filtering bookings by facility operator user_id:', userId); + whereClause = { + AND: [ + { facility: { operator: { user_id: userId } } }, + { OR: bookingSearchConditions } + ] + }; + } else if (userRole === 'ADMIN') { + whereClause = { OR: bookingSearchConditions }; + } else { + return []; + } + + const result = await prisma.booking.findMany({ + where: whereClause, + include: { + facility: { + select: { + id: true, + name: true, + type: true, + location: true, + pricePerDay: true + } + }, + farmer: { + select: { + id: true, + firstName: true, + lastName: true, + phone: true + } + } + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' } + }); + + console.log(`Found ${result.length} bookings`); + return result; + })(), + + // TRANSACTIONS SEARCH + (async () => { + console.log('🔍 Transactions search...'); + + const transactionSearchConditions: any[] = [ + { description: { contains: term, mode: "insensitive" } }, + { status: { contains: term, mode: "insensitive" } }, + { ref: { contains: term, mode: "insensitive" } }, + { booking: { facility: { name: { contains: term, mode: "insensitive" } } } }, + { booking: { farmer: { firstName: { contains: term, mode: "insensitive" } } } }, + { booking: { farmer: { lastName: { contains: term, mode: "insensitive" } } } } + ]; + + if (numericTerm !== null) { + transactionSearchConditions.push( + { amount: { equals: numericTerm } }, + { amount: { lte: numericTerm * 1.1, gte: numericTerm * 0.9 } } + ); + } + + let whereClause: any = {}; + + if (userRole === 'FARMER') { + whereClause = { + AND: [ + { booking: { farmer: { user_id: userId } } }, + { OR: transactionSearchConditions } + ] + }; + } else if (userRole === 'OPERATOR') { + whereClause = { + AND: [ + { booking: { facility: { operator: { user_id: userId } } } }, + { OR: transactionSearchConditions } + ] + }; + } else if (userRole === 'ADMIN') { + whereClause = { OR: transactionSearchConditions }; + } else { + return []; + } + + const result = await prisma.transaction.findMany({ + where: whereClause, + include: { + booking: { + include: { + facility: { select: { name: true, type: true, location: true } }, + farmer: { select: { firstName: true, lastName: true, phone: true } } + } + } + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' } + }); + + console.log(`Found ${result.length} transactions`); + return result; + })(), + + // NOTIFICATIONS SEARCH + (async () => { + console.log('🔍 Notifications search...'); + + const result = await prisma.notification.findMany({ + where: { + AND: [ + { userId: userId }, + { + OR: [ + { title: { contains: term, mode: "insensitive" } }, + { message: { contains: term, mode: "insensitive" } }, + { type: { contains: term, mode: "insensitive" } } + ] + } + ] + }, + skip, + take: limit, + orderBy: { createdAt: 'desc' } + }); + + console.log(`Found ${result.length} notifications`); + return result; + })() + ]); + + return { + farmers, + operators, + facilities, + bookings, + transactions, + notifications, + }; + } catch (error) { - throw new BadRequestError({ - message: "Error searching facilities", - from: "searchFacilities()", - cause: error, + throw error; + } +}; + + +export const searchFacilitiesWithFilters = async ( + term: string, + filters: FacilitySearchFilters, + userId: string, + userRole: string, + page: number = 1, + limit: number = 10 +) => { + console.log('🔍 Facility search with filters...'); + console.log('Search term:', term); + console.log('Filters:', filters); + + const skip = (page - 1) * limit; + const numericTerm = !isNaN(Number(term)) ? Number(term) : null; + + // Build search conditions + const searchConditions: any[] = []; + + // Text search conditions + if (term.trim()) { + searchConditions.push( + { name: { contains: term, mode: "insensitive" } }, + { description: { contains: term, mode: "insensitive" } }, + { location: { contains: term, mode: "insensitive" } }, + { operator: { firstName: { contains: term, mode: "insensitive" } } }, + { operator: { lastName: { contains: term, mode: "insensitive" } } }, + { operator: { businessName: { contains: term, mode: "insensitive" } } } + ); + + // Facility type search + const facilityTypeMapping = { + 'STORAGE': ['store', 'storage', 'warehouse', 'depot'], + 'DRYER': ['dry', 'dryer', 'drying', 'dehydrate'], + 'PROCESSING': ['process', 'processing', 'manufacture', 'production'], + 'OTHER': ['other', 'misc', 'miscellaneous'] + }; + + Object.entries(facilityTypeMapping).forEach(([type, keywords]) => { + keywords.forEach(keyword => { + if (term.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(term.toLowerCase())) { + searchConditions.push({ type: { equals: type as FacilityType } }); + } + }); }); + + // Direct type match + const upperTerm = term.toUpperCase(); + if (Object.values(FacilityType).includes(upperTerm as FacilityType)) { + searchConditions.push({ type: { equals: upperTerm as FacilityType } }); + } + + // Price search + if (numericTerm !== null) { + searchConditions.push( + { pricePerDay: { equals: numericTerm } }, + { pricePerDay: { lte: numericTerm * 1.1, gte: numericTerm * 0.9 } } + ); + } + } + + // Build filter conditions + const filterConditions: any = {}; + + if (filters.location) { + filterConditions.location = { contains: filters.location, mode: "insensitive" }; + } + + if (filters.type) { + filterConditions.type = { equals: filters.type }; + } + + if (filters.available !== undefined) { + filterConditions.available = { equals: filters.available }; + } + + if (filters.minPrice !== undefined || filters.maxPrice !== undefined) { + filterConditions.pricePerDay = {}; + if (filters.minPrice !== undefined) { + filterConditions.pricePerDay.gte = filters.minPrice; + } + if (filters.maxPrice !== undefined) { + filterConditions.pricePerDay.lte = filters.maxPrice; + } + } + + if (filters.minCapacity !== undefined || filters.maxCapacity !== undefined) { + filterConditions.capacity = {}; + if (filters.minCapacity !== undefined) { + filterConditions.capacity.gte = filters.minCapacity; + } + if (filters.maxCapacity !== undefined) { + filterConditions.capacity.lte = filters.maxCapacity; + } + } + + // Combine search and filter conditions + let whereClause: any = {}; + + const conditions = []; + + if (searchConditions.length > 0) { + conditions.push({ OR: searchConditions }); + } + + if (Object.keys(filterConditions).length > 0) { + conditions.push(filterConditions); + } + + if (userRole === 'OPERATOR') { + conditions.push({ operator: { user_id: userId } }); + } + + whereClause = conditions.length > 0 ? { AND: conditions } : {}; + + try { + const [facilities, totalCount] = await Promise.all([ + prisma.facility.findMany({ + where: whereClause, + include: { + operator: { + select: { + id: true, + firstName: true, + lastName: true, + phone: true, + businessName: true, + address: true, + user_id: true + } + } + }, + skip, + take: limit, + orderBy: [ + { available: 'desc' }, + { name: 'asc' } + ] + }), + prisma.facility.count({ where: whereClause }) + ]); + + console.log(`Found ${facilities.length} facilities out of ${totalCount} total`); + + return { + facilities, + totalCount, + currentPage: page, + totalPages: Math.ceil(totalCount / limit), + hasNextPage: page * limit < totalCount, + hasPrevPage: page > 1 + }; + + } catch (error) { + throw error; } }; diff --git a/src/types/types.ts b/src/types/types.ts index db544a7..ae8c354 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -158,6 +158,17 @@ export interface FacilityFilterOptions { maxPrice?: number; }; +export interface FacilitySearchFilters { + location?: string; + type?: 'DRYER' | 'STORAGE' | 'PROCESSING' | 'COLDROOM' | 'OTHER'; + available?: boolean; + operatorName?: string; + minPrice?: number; + maxPrice?: number; + minCapacity?: number; + maxCapacity?: number; +} + export interface GetByOperatorOptions { operatorId: bigint; page: number; From e38c5d0db8339d885070b7f99777edcadcb9282a Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 19:41:09 +0100 Subject: [PATCH 2/7] global search --- src/controllers/booking.controller.ts | 38 ++++- src/controllers/facility.controller.ts | 222 ++++++++++++++++--------- src/routes/booking.routes.ts | 5 +- src/routes/facility.routes.ts | 7 +- src/services/db/facility.service.ts | 99 +++++------ 5 files changed, 226 insertions(+), 145 deletions(-) diff --git a/src/controllers/booking.controller.ts b/src/controllers/booking.controller.ts index dc76c53..17960ca 100644 --- a/src/controllers/booking.controller.ts +++ b/src/controllers/booking.controller.ts @@ -336,4 +336,40 @@ export const updateBookingHandler = async (req: Request, res: Response, next: Ne } catch (error) { next(error) } -}; \ No newline at end of file +}; +export const getTodaysOperatorBookings = async ( + req: Request, + res: Response, + next: NextFunction +): Promise => { + try { + const operatorId = req.operator.id; + const today = new Date(); + const startOfDay = new Date(today.setHours(0, 0, 0, 0)); + const endOfDay = new Date(today.setHours(23, 59, 59, 999)); + + const bookings = await prisma.booking.findMany({ + where: { + facility: { + operatorId: operatorId, + }, + startDate: { + gte: startOfDay, + lte: endOfDay, + }, + }, + include: { + facility: true, + farmer: true, + }, + }); + + res.status(StatusCodes.OK).json({ + success: true, + message: `Operator's bookings for today`, + data: bookings, + }); + } catch (error) { + next(error); + } +}; diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index ba070ed..d4fce52 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -250,7 +250,7 @@ export const updateCapacity = async ( if (!parsedCapacity || isNaN(parsedCapacity) || parsedCapacity < 0) { throw new BadRequestError({ - message: "Capacity musst be a positive number", + message: "Capacity must be a positive number", from: "updateCapacity", }); } @@ -270,124 +270,186 @@ export const updateCapacity = async ( }; +export const globalFacilitySearch = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const { term = "", page = 1, limit = 10 } = req.query; + const userId = req.currentUser?.id; + const userRole = req.currentUser?.role; + + if (!userId) { + res.status(StatusCodes.UNAUTHORIZED).json({ + message: "Authentication required" + }); + return + } + + const results = await searchEverythingGlobally( + term as string, + userId, + userRole, + Number(page), + Number(limit) + ); + + res.status(StatusCodes.OK).json({ + message: "Search results fetched successfully", + data: results, + }); + + } catch (error) { + next(error); + } +}; + + + +// UPDATED CONTROLLER + + // export const globalFacilitySearch = async ( // req: Request, // res: Response, // next: NextFunction // ) => { // try { -// const { term = "", page = 1, limit = 10 } = req.query; -// const userId = req.currentUser?.id; +// const { +// term = "", +// page = 1, +// limit = 10, +// location, +// type, +// available, +// minPrice, +// maxPrice, +// minCapacity, +// maxCapacity +// } = req.query; + +// const userId = req.currentUser?.id; // const userRole = req.currentUser?.role; // if (!userId) { -// res.status(StatusCodes.UNAUTHORIZED).json({ +// res.status(StatusCodes.FORBIDDEN).json({ // message: "Authentication required" // }); -// return +// return; // } -// const results = await searchEverythingGlobally( -// term as string, -// userId, -// userRole, -// Number(page), -// Number(limit) -// ); +// // Check if filters are provided +// const hasFilters = location || type || available !== undefined || +// minPrice || maxPrice || minCapacity || maxCapacity; + +// let results; + +// if (hasFilters) { +// // Use filtered search +// const filters: FacilitySearchFilters = { +// ...(location && { location: location as string }), +// ...(type && { type: type as FacilityType }), +// ...(available !== undefined && { available: available === 'true' }), +// ...(minPrice && { minPrice: Number(minPrice) }), +// ...(maxPrice && { maxPrice: Number(maxPrice) }), +// ...(minCapacity && { minCapacity: Number(minCapacity) }), +// ...(maxCapacity && { maxCapacity: Number(maxCapacity) }) +// }; + +// results = await searchFacilitiesWithFilters( +// term as string, +// filters, +// userId, +// userRole, +// Number(page), +// Number(limit) +// ); +// } else { +// // Use global search +// results = await searchEverythingGlobally( +// term as string, +// userId, +// userRole, +// Number(page), +// Number(limit) +// ); +// } // res.status(StatusCodes.OK).json({ // message: "Search results fetched successfully", // data: results, +// searchTerm: term, +// userRole, +// hasFilters // }); // } catch (error) { +// console.error('Controller error:', error); // next(error); // } // }; - -// UPDATED CONTROLLER - - -export const globalFacilitySearch = async ( +export const getOperatorsAvailableFacilities = async ( req: Request, res: Response, next: NextFunction ) => { try { - const { - term = "", - page = 1, - limit = 10, - location, - type, - available, - minPrice, - maxPrice, - minCapacity, - maxCapacity - } = req.query; - - const userId = req.currentUser?.id; - const userRole = req.currentUser?.role; - - if (!userId) { - res.status(StatusCodes.FORBIDDEN).json({ - message: "Authentication required" + const operator = req.operator; + + if (!operator) { + throw new UnauthorizedError({ + message: "Operator not authorized", + from: "getOperatorsAvailableFacilities controller" }); - return; } - // Check if filters are provided - const hasFilters = location || type || available !== undefined || - minPrice || maxPrice || minCapacity || maxCapacity; - - let results; - - if (hasFilters) { - // Use filtered search - const filters: FacilitySearchFilters = { - ...(location && { location: location as string }), - ...(type && { type: type as FacilityType }), - ...(available !== undefined && { available: available === 'true' }), - ...(minPrice && { minPrice: Number(minPrice) }), - ...(maxPrice && { maxPrice: Number(maxPrice) }), - ...(minCapacity && { minCapacity: Number(minCapacity) }), - ...(maxCapacity && { maxCapacity: Number(maxCapacity) }) - }; - - results = await searchFacilitiesWithFilters( - term as string, - filters, - userId, - userRole, - Number(page), - Number(limit) - ); - } else { - // Use global search - results = await searchEverythingGlobally( - term as string, - userId, - userRole, - Number(page), - Number(limit) - ); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + const endOfToday = new Date(today.getTime() + 24 * 60 * 60 * 1000); + + const facilities = await prisma.facility.findMany({ + where: { operatorId: operator.id }, + }); + + if (!facilities.length) { + res.status(StatusCodes.OK).json({ + message: "Operator has no facilities", + facilities: [], + }); + return; } + const facilityIds = facilities.map((f) => f.id); + + const bookedToday = await prisma.booking.findMany({ + where: { + facilityId: { in: facilityIds }, + startDate: { + gte: today, + lt: endOfToday, + }, + status: { + in: ["RESERVED", "CONFIRMED"], + }, + }, + }); + + const bookedFacilityIds = new Set(bookedToday.map((b) => b.facilityId)); + + const availableFacilities = facilities.filter( + (f) => !bookedFacilityIds.has(f.id) + ); + res.status(StatusCodes.OK).json({ - message: "Search results fetched successfully", - data: results, - searchTerm: term, - userRole, - hasFilters + message: "Available facilities fetched successfully", + facilities: availableFacilities, }); } catch (error) { - console.error('Controller error:', error); next(error); } }; - - diff --git a/src/routes/booking.routes.ts b/src/routes/booking.routes.ts index 4d53724..eb0da75 100644 --- a/src/routes/booking.routes.ts +++ b/src/routes/booking.routes.ts @@ -4,7 +4,8 @@ import { isFarmer, isAuthorizedFarmer, isAuthorizedOperator, isOperator } from ' import { verifyAuth } from '../middlewares/authenticate.middleware'; import { deleteBookingHandler, expireBooking, fetchBookingById, listAllFacilitiesBookings, listFarmerBookings, - approveOrRejectBookingHandler, updateBookingHandler, createBookingHandler, getTotalApprovedBookings } + approveOrRejectBookingHandler, updateBookingHandler, createBookingHandler, getTotalApprovedBookings, + getTodaysOperatorBookings } from '../controllers/booking.controller'; import { preventDateUpdateIfPaid } from '../middlewares/bookingDateValidator'; @@ -16,6 +17,8 @@ router.post('/', verifyAuth, createBookingHandler); router.get('/farmer/me', verifyAuth, isFarmer, isAuthorizedFarmer, listFarmerBookings); router.get('/operator/me', verifyAuth, isOperator, isAuthorizedOperator, listAllFacilitiesBookings); router.get('/approved-bookings',verifyAuth, isOperator, isAuthorizedOperator, getTotalApprovedBookings); +router.get('/operator/today', verifyAuth, isOperator, isAuthorizedOperator, getTodaysOperatorBookings); + router.get('/:bookingId', verifyAuth, validateBookingId, fetchBookingById); router.patch('/:bookingId', verifyAuth, isFarmer, isAuthorizedFarmer, validateBookingId, preventDateUpdateIfPaid, updateBookingHandler); diff --git a/src/routes/facility.routes.ts b/src/routes/facility.routes.ts index 0aea2b7..8a53f77 100644 --- a/src/routes/facility.routes.ts +++ b/src/routes/facility.routes.ts @@ -2,7 +2,8 @@ import express from 'express'; import { facilityValidator } from '../utils/validateFacility'; import { verifyAuth } from '../middlewares/authenticate.middleware'; import { isAuthorizedOperator, isOperator, isFacilityOwner } from '../middlewares/authorization.middlewares'; -import { addFacility, getFacility, updateFacility, getAllFacilityByFiltering, deleteFacility, updateCapacity , deleteFacilityImage, getFacilitiesByOperatorController, globalFacilitySearch } from '../controllers/facility.controller'; +import { addFacility, getFacility, updateFacility, getAllFacilityByFiltering, deleteFacility, updateCapacity , deleteFacilityImage, getFacilitiesByOperatorController, globalFacilitySearch, getOperatorsAvailableFacilities } from '../controllers/facility.controller'; + import { upload } from '../config/config.cloudinary'; import { uploadFacilityImage } from '../controllers/cloudinary.controller'; @@ -13,7 +14,9 @@ router.post('/', verifyAuth, facilityValidator, addFacility); router.get('/', verifyAuth, getAllFacilityByFiltering); //Allows filtering and no filtering router.get('/search', verifyAuth, globalFacilitySearch) router.get('/all', verifyAuth, isAuthorizedOperator, getFacilitiesByOperatorController); -router.post('/images', verifyAuth, isOperator, upload.array('images', 5), uploadFacilityImage); +router.get('/available/today', verifyAuth, isAuthorizedOperator, getOperatorsAvailableFacilities); +router.post('/images', verifyAuth, isOperator, upload.array('images', 5), uploadFacilityImage); + router.get('/:facilityId', verifyAuth, getFacility); router.put('/:facilityId', verifyAuth, isAuthorizedOperator, isFacilityOwner, updateFacility); diff --git a/src/services/db/facility.service.ts b/src/services/db/facility.service.ts index 8b22248..e242ccc 100644 --- a/src/services/db/facility.service.ts +++ b/src/services/db/facility.service.ts @@ -225,24 +225,22 @@ export const searchEverythingGlobally = async ( const skip = (page - 1) * limit; const numericTerm = !isNaN(Number(term)) ? Number(term) : null; - if (!term.trim() || /^[@#\$%\^&\*\(\)_\+\-=\[\]\{\}\|;':\",./<>\?~`!]+$/.test(term)) { - return { - farmers: [], - operators: [], - facilities: [], - bookings: [], - transactions: [], - notifications: [], - }; - } + if (!term.trim() || /^[@#\$%\^&\*\(\)_\+\-=\[\]\{\}\|;':\",./<>\?~`!]+$/.test(term)) { + return { + farmers: [], + operators: [], + facilities: [], + bookings: [], + transactions: [], + notifications: [], + }; + } - try { - const [farmers, operators, facilities, bookings, transactions, notifications] = await Promise.all([ + try { + const [farmers, operators, facilities, bookings, transactions, notifications] = await Promise.all([ // FARMERS SEARCH (Admin only) - userRole === 'ADMIN' ? (async () => { - console.log('🔍 Searching farmers (admin access)...'); - + userRole === 'ADMIN' ? (async () => { const result = await prisma.farmer.findMany({ where: { OR: [ @@ -260,15 +258,11 @@ export const searchEverythingGlobally = async ( { lastName: 'asc' } ] }); - - console.log(`Found ${result.length} farmers`); return result; })() : [], // OPERATORS SEARCH (Admin only) userRole === 'ADMIN' ? (async () => { - console.log('🔍 Searching operators (admin access)...'); - const result = await prisma.operator.findMany({ where: { OR: [ @@ -292,15 +286,13 @@ export const searchEverythingGlobally = async ( return result; })() : [], - // FACILITIES SEARCH - FIXED: Using prisma.facility instead of prisma.booking (async () => { console.log('🔍 Facilities search - GLOBAL for farmers...'); - - // Build facility search conditions const facilitySearchConditions: any[] = [ { name: { contains: term, mode: "insensitive" } }, { description: { contains: term, mode: "insensitive" } }, - { location: { contains: term, mode: "insensitive" } } + { location: { contains: term, mode: "insensitive" } }, + { capacity: { contains: term, mode: "insensitive" } }, ]; // Enhanced facility type matching @@ -308,6 +300,7 @@ export const searchEverythingGlobally = async ( 'STORAGE': ['store', 'storage', 'warehouse', 'depot'], 'DRYER': ['dry', 'dryer', 'drying', 'dehydrate'], 'PROCESSING': ['process', 'processing', 'manufacture', 'production'], + 'COLDROOM': ['coldroom', 'cold', 'decay', 'preserve', 'cooling', 'cool'], 'OTHER': ['other', 'misc', 'miscellaneous'] }; @@ -323,27 +316,26 @@ export const searchEverythingGlobally = async ( const statusKeywordMap = { 'RESERVED': ['reserved', 'pending', 'wait', 'waiting', 'review'], - 'CONFIRMED': ['confirmed', 'approved', 'accept', 'accepted'], + 'CONFIRMED': ['confirmed', 'approved', 'accept', 'accepted', 'completed', 'done', 'finished'], 'CANCELLED': ['cancelled', 'canceled', 'reject', 'rejected'], - 'COMPLETED': ['completed', 'done', 'finished'], }; -Object.entries(statusKeywordMap).forEach(([status, keywords]) => { - keywords.forEach(keyword => { - if ( - term.toLowerCase().includes(keyword.toLowerCase()) || - keyword.toLowerCase().includes(term.toLowerCase()) - ) { - facilitySearchConditions.push({ - bookings: { - some: { - status: status - } - } - }); - } - }); -}); + Object.entries(statusKeywordMap).forEach(([status, keywords]) => { + keywords.forEach(keyword => { + if ( + term.toLowerCase().includes(keyword.toLowerCase()) || + keyword.toLowerCase().includes(term.toLowerCase()) + ) { + facilitySearchConditions.push({ + bookings: { + some: { + status: status + } + } + }); + } + }); + }); // Direct facility type match const upperTerm = term.toUpperCase(); if (Object.values(FacilityType).includes(upperTerm as FacilityType)) { @@ -366,9 +358,8 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { ); let whereClause: any = {}; - + // Only show facilities managed by the logged-in operator if (userRole === 'OPERATOR') { - console.log('🔒 Operator: Filtering facilities by operator user_id:', userId); whereClause = { AND: [ { operator: { user_id: userId } }, @@ -377,7 +368,6 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { }; } else { // FARMER and ADMIN can see ALL facilities - console.log('✅ Global facility search - showing ALL facilities'); whereClause = { OR: facilitySearchConditions }; } @@ -401,7 +391,7 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { skip, take: limit, orderBy: [ - { available: 'desc' }, // Available facilities first + { available: 'desc' }, { name: 'asc' } ] }); @@ -410,20 +400,17 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { return result; })(), - // BOOKINGS SEARCH - FIXED: Proper enum values + // BOOKINGS SEARCH (async () => { - console.log('🔍 Bookings search...'); - - // Build booking search conditions const bookingSearchConditions: any[] = [ { facility: { name: { contains: term, mode: "insensitive" } } }, { facility: { location: { contains: term, mode: "insensitive" } } }, + { facility: { type: { contains: term, mode: "insensitive" } } }, { farmer: { firstName: { contains: term, mode: "insensitive" } } }, { farmer: { lastName: { contains: term, mode: "insensitive" } } }, { farmer: { phone: { contains: term, mode: "insensitive" } } } ]; - // FIXED: Booking status search with correct enum values const statusMapping = { 'RESERVED': ['reserved', 'pending', 'wait', 'waiting', 'review'], 'CONFIRMED': ['confirmed', 'approved', 'accept', 'accepted'], @@ -441,14 +428,12 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { }); }); - // Direct status match - FIXED: Check against correct enum const upperTerm = term.toUpperCase(); const validStatuses = ['RESERVED', 'CONFIRMED', 'CANCELLED', 'COMPLETED']; if (validStatuses.includes(upperTerm)) { bookingSearchConditions.push({ status: { equals: upperTerm } }); } - // Numeric amount search if (numericTerm !== null) { bookingSearchConditions.push( { amount: { equals: numericTerm } }, @@ -593,7 +578,6 @@ Object.entries(statusKeywordMap).forEach(([status, keywords]) => { orderBy: { createdAt: 'desc' } }); - console.log(`Found ${result.length} notifications`); return result; })() ]); @@ -621,17 +605,10 @@ export const searchFacilitiesWithFilters = async ( page: number = 1, limit: number = 10 ) => { - console.log('🔍 Facility search with filters...'); - console.log('Search term:', term); - console.log('Filters:', filters); - const skip = (page - 1) * limit; const numericTerm = !isNaN(Number(term)) ? Number(term) : null; - // Build search conditions const searchConditions: any[] = []; - - // Text search conditions if (term.trim()) { searchConditions.push( { name: { contains: term, mode: "insensitive" } }, @@ -709,7 +686,6 @@ export const searchFacilitiesWithFilters = async ( } } - // Combine search and filter conditions let whereClause: any = {}; const conditions = []; @@ -770,3 +746,4 @@ export const searchFacilitiesWithFilters = async ( throw error; } }; + From 379c7f1de14cf9901ec44b9a8a8c88a4dc669c79 Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 19:43:17 +0100 Subject: [PATCH 3/7] feat: global search --- src/controllers/facility.controller.ts | 86 -------------------------- 1 file changed, 86 deletions(-) diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index d4fce52..3e8eca3 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -305,92 +305,6 @@ export const globalFacilitySearch = async ( } }; - - -// UPDATED CONTROLLER - - -// export const globalFacilitySearch = async ( -// req: Request, -// res: Response, -// next: NextFunction -// ) => { -// try { -// const { -// term = "", -// page = 1, -// limit = 10, -// location, -// type, -// available, -// minPrice, -// maxPrice, -// minCapacity, -// maxCapacity -// } = req.query; - -// const userId = req.currentUser?.id; -// const userRole = req.currentUser?.role; - -// if (!userId) { -// res.status(StatusCodes.FORBIDDEN).json({ -// message: "Authentication required" -// }); -// return; -// } - -// // Check if filters are provided -// const hasFilters = location || type || available !== undefined || -// minPrice || maxPrice || minCapacity || maxCapacity; - -// let results; - -// if (hasFilters) { -// // Use filtered search -// const filters: FacilitySearchFilters = { -// ...(location && { location: location as string }), -// ...(type && { type: type as FacilityType }), -// ...(available !== undefined && { available: available === 'true' }), -// ...(minPrice && { minPrice: Number(minPrice) }), -// ...(maxPrice && { maxPrice: Number(maxPrice) }), -// ...(minCapacity && { minCapacity: Number(minCapacity) }), -// ...(maxCapacity && { maxCapacity: Number(maxCapacity) }) -// }; - -// results = await searchFacilitiesWithFilters( -// term as string, -// filters, -// userId, -// userRole, -// Number(page), -// Number(limit) -// ); -// } else { -// // Use global search -// results = await searchEverythingGlobally( -// term as string, -// userId, -// userRole, -// Number(page), -// Number(limit) -// ); -// } - -// res.status(StatusCodes.OK).json({ -// message: "Search results fetched successfully", -// data: results, -// searchTerm: term, -// userRole, -// hasFilters -// }); - -// } catch (error) { -// console.error('Controller error:', error); -// next(error); -// } -// }; - - export const getOperatorsAvailableFacilities = async ( req: Request, res: Response, From f10de93adb5092ff1e17ec44244e26cfca33226a Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 20:03:04 +0100 Subject: [PATCH 4/7] fix: lint fails --- src/controllers/facility.controller.ts | 6 ++-- src/services/db/facility.service.ts | 38 +++++++++----------------- 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index 3e8eca3..e0d2151 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -18,8 +18,6 @@ import { getFacilitiesByOperator, updateFacilityCapacity, searchEverythingGlobally, - searchFacilitiesWithFilters, - } from "../services/db/facility.service"; import { StatusCodes } from "http-status-codes"; import { @@ -27,9 +25,8 @@ import { NotFoundError, UnauthorizedError, } from "../errors/errors"; -import { FacilityType, PrismaClient } from "@prisma/client"; +import { PrismaClient } from "@prisma/client"; import { deleteImageFromCloudinary } from "../services/cloudinary.service"; -import { FacilityFilterOptions, FacilitySearchFilters } from "../types/types"; const prisma = new PrismaClient(); export const addFacility = async ( @@ -305,6 +302,7 @@ export const globalFacilitySearch = async ( } }; + export const getOperatorsAvailableFacilities = async ( req: Request, res: Response, diff --git a/src/services/db/facility.service.ts b/src/services/db/facility.service.ts index e242ccc..2e6cb17 100644 --- a/src/services/db/facility.service.ts +++ b/src/services/db/facility.service.ts @@ -1,4 +1,4 @@ -import { FacilityType, Prisma, booking_status } from "@prisma/client"; +import { FacilityType, Prisma} from "@prisma/client"; import { prisma } from "../../config/config.db"; import { BadRequestError, NotFoundError } from "../../errors/errors"; import { @@ -8,8 +8,6 @@ import { GetByOperatorOptions, UserRole, } from "../../types/types"; -import { Request, Response, NextFunction } from "express"; -import { StatusCodes } from "http-status-codes"; export const createFacility = async (data: Prisma.FacilityCreateInput) => { try { @@ -222,21 +220,19 @@ export const searchEverythingGlobally = async ( limit: number = 10 ) => { - const skip = (page - 1) * limit; - const numericTerm = !isNaN(Number(term)) ? Number(term) : null; - - if (!term.trim() || /^[@#\$%\^&\*\(\)_\+\-=\[\]\{\}\|;':\",./<>\?~`!]+$/.test(term)) { - return { - farmers: [], - operators: [], - facilities: [], - bookings: [], - transactions: [], - notifications: [], - }; - } + const skip = (page - 1) * limit; + const numericTerm = !isNaN(Number(term)) ? Number(term) : null; + if (!term.trim() || /^[@#$%^&*()_+\-=\[\]{}|;':",./<>?~`!]+$/.test(term)) { + return { + farmers: [], + operators: [], + facilities: [], + bookings: [], + transactions: [], + notifications: [], + }; + } - try { const [farmers, operators, facilities, bookings, transactions, notifications] = await Promise.all([ // FARMERS SEARCH (Admin only) @@ -591,9 +587,6 @@ export const searchEverythingGlobally = async ( notifications, }; - } catch (error) { - throw error; - } }; @@ -704,7 +697,6 @@ export const searchFacilitiesWithFilters = async ( whereClause = conditions.length > 0 ? { AND: conditions } : {}; - try { const [facilities, totalCount] = await Promise.all([ prisma.facility.findMany({ where: whereClause, @@ -741,9 +733,5 @@ export const searchFacilitiesWithFilters = async ( hasNextPage: page * limit < totalCount, hasPrevPage: page > 1 }; - - } catch (error) { - throw error; - } }; From cb6ba2640e828218bf0ec1d7983b99fe9e7cf96e Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 20:06:58 +0100 Subject: [PATCH 5/7] fix: lint fails --- src/services/db/facility.service.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/services/db/facility.service.ts b/src/services/db/facility.service.ts index 2e6cb17..2bc450e 100644 --- a/src/services/db/facility.service.ts +++ b/src/services/db/facility.service.ts @@ -222,16 +222,16 @@ export const searchEverythingGlobally = async ( const skip = (page - 1) * limit; const numericTerm = !isNaN(Number(term)) ? Number(term) : null; - if (!term.trim() || /^[@#$%^&*()_+\-=\[\]{}|;':",./<>?~`!]+$/.test(term)) { - return { - farmers: [], - operators: [], - facilities: [], - bookings: [], - transactions: [], - notifications: [], - }; - } + if (!term.trim() || /^[@#$%^&*()_+\-=[\]{}|;':",./<>?~`!]+$/.test(term)) { + return { + farmers: [], + operators: [], + facilities: [], + bookings: [], + transactions: [], + notifications: [], + }; + } const [farmers, operators, facilities, bookings, transactions, notifications] = await Promise.all([ From 4faed8f1d9f55eba157aa83c2066d4c065ed3f70 Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 20:13:39 +0100 Subject: [PATCH 6/7] fix: lint fails --- src/controllers/facility.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index e0d2151..1cbfc8a 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -1,6 +1,5 @@ import { NextFunction, Request, Response } from "express"; -// Extend Express Request interface to include decodeuser declare global { namespace Express { interface Request { From 79cb960bf1bc9f4aaf6ecd119a3ba69243ee0874 Mon Sep 17 00:00:00 2001 From: oboscole Date: Thu, 7 Aug 2025 20:19:54 +0100 Subject: [PATCH 7/7] fix: lint fails --- src/controllers/facility.controller.ts | 2 +- src/routes/facility.routes.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/facility.controller.ts b/src/controllers/facility.controller.ts index 1cbfc8a..623554b 100644 --- a/src/controllers/facility.controller.ts +++ b/src/controllers/facility.controller.ts @@ -302,7 +302,7 @@ export const globalFacilitySearch = async ( }; -export const getOperatorsAvailableFacilities = async ( +export const getOperatorAvailableFacilities = async ( req: Request, res: Response, next: NextFunction diff --git a/src/routes/facility.routes.ts b/src/routes/facility.routes.ts index 8a53f77..ea0e286 100644 --- a/src/routes/facility.routes.ts +++ b/src/routes/facility.routes.ts @@ -2,7 +2,7 @@ import express from 'express'; import { facilityValidator } from '../utils/validateFacility'; import { verifyAuth } from '../middlewares/authenticate.middleware'; import { isAuthorizedOperator, isOperator, isFacilityOwner } from '../middlewares/authorization.middlewares'; -import { addFacility, getFacility, updateFacility, getAllFacilityByFiltering, deleteFacility, updateCapacity , deleteFacilityImage, getFacilitiesByOperatorController, globalFacilitySearch, getOperatorsAvailableFacilities } from '../controllers/facility.controller'; +import { addFacility, getFacility, updateFacility, getAllFacilityByFiltering, deleteFacility, updateCapacity , deleteFacilityImage, getFacilitiesByOperatorController, globalFacilitySearch, getOperatorAvailableFacilities } from '../controllers/facility.controller'; import { upload } from '../config/config.cloudinary'; import { uploadFacilityImage } from '../controllers/cloudinary.controller'; @@ -14,7 +14,7 @@ router.post('/', verifyAuth, facilityValidator, addFacility); router.get('/', verifyAuth, getAllFacilityByFiltering); //Allows filtering and no filtering router.get('/search', verifyAuth, globalFacilitySearch) router.get('/all', verifyAuth, isAuthorizedOperator, getFacilitiesByOperatorController); -router.get('/available/today', verifyAuth, isAuthorizedOperator, getOperatorsAvailableFacilities); +router.get('/available/today', verifyAuth, isAuthorizedOperator, getOperatorAvailableFacilities); router.post('/images', verifyAuth, isOperator, upload.array('images', 5), uploadFacilityImage);