11import { Request , Response } from 'express' ;
22import Faq from '../models/faqsModel.js' ;
33import { asyncHandler } from '../utils/asyncHandler.js' ;
4- import { sendSuccess , sendCreated , sendNotFound } from '../utils/apiResponses.js' ;
4+ import { sendSuccess , sendCreated , sendNotFound , sendBadRequest , sendPaginatedSuccess } from '../utils/apiResponses.js' ;
5+ import { validateFaq } from '../schemas/faqSchema.js' ;
6+ import { ROLE_PREFIXES , ROLES } from '../constants/roles.js' ;
57
6- // @desc Get all FAQs
8+ // @desc Get all FAQs with optional filtering and pagination
79// @route GET /api/faqs
810// @access Private
911export const getFaqs = asyncHandler ( async ( req : Request , res : Response ) => {
10- const faqs = await Faq . find ( ) . sort ( 'SortPosition' ) . lean ( ) ;
11- return sendSuccess ( res , faqs ) ;
12+ const {
13+ location,
14+ search,
15+ page = 1 ,
16+ limit = 10 ,
17+ sortBy = 'DocumentModifiedDate' ,
18+ sortOrder = 'desc'
19+ } = req . query ;
20+
21+ const query : any = { } ;
22+ const conditions : any [ ] = [ ] ;
23+
24+ // Get user auth claims for role-based filtering
25+ const requestingUserAuthClaims = req . user ?. AuthClaims || [ ] ;
26+ // const userId = req.user?._id;
27+
28+ // Role-based filtering
29+ const isSuperAdmin = requestingUserAuthClaims . includes ( ROLES . SUPER_ADMIN ) ;
30+ const isVolunteerAdmin = requestingUserAuthClaims . includes ( ROLES . VOLUNTEER_ADMIN ) ;
31+ const isCityAdmin = requestingUserAuthClaims . includes ( ROLES . CITY_ADMIN ) ;
32+
33+ // CityAdmin: only see organisations from their cities
34+ if ( isCityAdmin && ! isSuperAdmin && ! isVolunteerAdmin ) {
35+ const cityAdminLocations = requestingUserAuthClaims
36+ . filter ( claim => claim . startsWith ( ROLE_PREFIXES . CITY_ADMIN_FOR ) )
37+ . map ( claim => claim . replace ( ROLE_PREFIXES . CITY_ADMIN_FOR , '' ) ) ;
38+
39+ if ( cityAdminLocations . length > 0 ) {
40+ conditions . push ( { LocationKey : { $in : cityAdminLocations } } ) ;
41+ }
42+ }
43+
44+ // Apply search filter - search by Title or Body
45+ if ( search && typeof search === 'string' ) {
46+ const searchTerm = search . trim ( ) ;
47+ conditions . push ( {
48+ $or : [
49+ { Title : { $regex : searchTerm , $options : 'i' } } ,
50+ { Body : { $regex : searchTerm , $options : 'i' } }
51+ ]
52+ } ) ;
53+ }
54+
55+ // Apply location filter
56+ if ( location && typeof location === 'string' ) {
57+ conditions . push ( { LocationKey : location } ) ;
58+ }
59+
60+ // Combine all conditions with AND logic
61+ if ( conditions . length > 0 ) {
62+ query . $and = conditions ;
63+ }
64+
65+ // Pagination
66+ const skip = ( Number ( page ) - 1 ) * Number ( limit ) ;
67+
68+ // Sort options
69+ const sortOptions : any = { } ;
70+ sortOptions [ sortBy as string ] = sortOrder === 'desc' ? - 1 : 1 ;
71+
72+ const faqs = await Faq . find ( query )
73+ . sort ( sortOptions )
74+ . skip ( skip )
75+ . limit ( Number ( limit ) )
76+ . lean ( ) ;
77+
78+ // Get total count using the same query
79+ const total = await Faq . countDocuments ( query ) ;
80+
81+ return sendPaginatedSuccess ( res , faqs , {
82+ page : Number ( page ) ,
83+ limit : Number ( limit ) ,
84+ total,
85+ pages : Math . ceil ( total / Number ( limit ) )
86+ } ) ;
1287} ) ;
1388
1489// @desc Get single FAQ by ID
@@ -22,27 +97,66 @@ export const getFaqById = asyncHandler(async (req: Request, res: Response) => {
2297 return sendSuccess ( res , faq ) ;
2398} ) ;
2499
25-
26100// @desc Create new FAQ
27101// @route POST /api/faqs
28102// @access Private
29103export const createFaq = asyncHandler ( async ( req : Request , res : Response ) => {
30- const faq = await Faq . create ( req . body ) ;
104+ // Validate the request data
105+ const validation = validateFaq ( req . body ) ;
106+
107+ if ( ! validation . success ) {
108+ const errorMessages = validation . errors . map ( err => err . message ) . join ( ', ' ) ;
109+ return sendBadRequest ( res , `Validation failed: ${ errorMessages } ` ) ;
110+ }
111+
112+ if ( ! validation . data ) {
113+ return sendBadRequest ( res , 'Validation data is missing' ) ;
114+ }
115+
116+ // Add system fields
117+ const faqData = {
118+ ...validation . data ,
119+ CreatedBy : req . user ?. _id || req . body ?. CreatedBy ,
120+ DocumentCreationDate : new Date ( ) ,
121+ DocumentModifiedDate : new Date ( ) ,
122+ } ;
123+
124+ const faq = await Faq . create ( faqData ) ;
31125 return sendCreated ( res , faq ) ;
32126} ) ;
33127
34128// @desc Update FAQ
35129// @route PUT /api/faqs/:id
36130// @access Private
37131export const updateFaq = asyncHandler ( async ( req : Request , res : Response ) => {
132+ // Check if FAQ exists
133+ const existingFaq = await Faq . findById ( req . params . id ) ;
134+ if ( ! existingFaq ) {
135+ return sendNotFound ( res , 'FAQ not found' ) ;
136+ }
137+
138+ // Validate the request data
139+ const validation = validateFaq ( req . body ) ;
140+
141+ if ( ! validation . success ) {
142+ const errorMessages = validation . errors . map ( err => err . message ) . join ( ', ' ) ;
143+ return sendBadRequest ( res , `Validation failed: ${ errorMessages } ` ) ;
144+ }
145+
146+ if ( ! validation . data ) {
147+ return sendBadRequest ( res , 'Validation data is missing' ) ;
148+ }
149+
150+ // Update FAQ with validated data
38151 const faq = await Faq . findByIdAndUpdate (
39- req . params . id ,
40- req . body ,
152+ req . params . id ,
153+ {
154+ ...validation . data ,
155+ DocumentModifiedDate : new Date ( )
156+ } ,
41157 { new : true , runValidators : true }
42158 ) ;
43- if ( ! faq ) {
44- return sendNotFound ( res , 'FAQ not found' ) ;
45- }
159+
46160 return sendSuccess ( res , faq ) ;
47161} ) ;
48162
0 commit comments