11import { Request , Response } from 'express' ;
22import { asyncHandler } from '../utils/asyncHandler.js' ;
3- import { BackgroundType , BannerTemplateType } from '../types/index.js' ;
3+ import { BackgroundType } from '../types/index.js' ;
44import { validateBanner } from '../schemas/bannerSchema.js' ;
55import Banner from '../models/bannerModel.js' ;
66import { deleteFile } from '../middleware/uploadMiddleware.js' ;
@@ -9,23 +9,22 @@ import { sendSuccess, sendCreated, sendBadRequest, sendNotFound, sendPaginatedSu
99
1010// Get all banners with optional filtering
1111export const getBanners = asyncHandler ( async ( req : Request , res : Response ) => {
12- const {
12+ const {
1313 location,
14- locations, // New: comma-separated list of locations for CityAdmin filtering
15- templateType,
16- isActive,
14+ locations,
15+ isActive,
1716 search,
18- page = 1 ,
17+ page = 1 ,
1918 limit = 9 ,
2019 sortBy = 'Priority' ,
2120 sortOrder = 'asc'
2221 } = req . query ;
2322
2423 const query : any = { } ;
25-
24+
2625 // Apply search filter
2726 if ( search && typeof search === 'string' ) {
28- const searchRegex = new RegExp ( search , 'i' ) ; // Case-insensitive search
27+ const searchRegex = new RegExp ( search , 'i' ) ;
2928 query . $or = [
3029 { Title : searchRegex } ,
3130 { Description : searchRegex } ,
@@ -34,9 +33,7 @@ export const getBanners = asyncHandler(async (req: Request, res: Response) => {
3433 }
3534
3635 // Apply location filters
37- // Priority: 'locations' (for CityAdmin bulk filtering) over 'location' (for single filter)
3836 if ( locations && typeof locations === 'string' ) {
39- // Multiple locations passed from admin side for CityAdmin users
4037 const locationArray = locations . split ( ',' ) . map ( loc => loc . trim ( ) ) . filter ( Boolean ) ;
4138 if ( locationArray . length > 0 ) {
4239 const locationQuery = {
@@ -46,44 +43,37 @@ export const getBanners = asyncHandler(async (req: Request, res: Response) => {
4643 { LocationSlug : null }
4744 ]
4845 } ;
49-
50- // Combine with search query if it exists
46+
5147 if ( query . $or ) {
5248 query . $and = [
53- { $or : query . $or } , // Search conditions
54- locationQuery // Location conditions
49+ { $or : query . $or } ,
50+ locationQuery
5551 ] ;
5652 delete query . $or ;
5753 } else {
5854 query . $or = locationQuery . $or ;
5955 }
6056 }
6157 } else if ( location && typeof location === 'string' ) {
62- // Single location filter from UI
6358 const locationQuery = {
6459 $or : [
6560 { LocationSlug : location } ,
6661 { LocationSlug : { $exists : false } } ,
6762 { LocationSlug : null }
6863 ]
6964 } ;
70-
71- // Combine with search query if it exists
65+
7266 if ( query . $or ) {
7367 query . $and = [
74- { $or : query . $or } , // Search conditions
75- locationQuery // Location conditions
68+ { $or : query . $or } ,
69+ locationQuery
7670 ] ;
7771 delete query . $or ;
7872 } else {
7973 query . $or = locationQuery . $or ;
8074 }
8175 }
82-
83- if ( templateType ) {
84- query . TemplateType = templateType ;
85- }
86-
76+
8777 if ( isActive !== undefined ) {
8878 query . IsActive = isActive === 'true' ;
8979 }
@@ -130,19 +120,16 @@ export const createBanner = asyncHandler(async (req: Request, res: Response) =>
130120
131121 // Validate and transform banner data using Zod (final validation after upload)
132122 const validation = validateBanner ( processedData ) ;
133-
123+
134124 if ( ! validation . success ) {
135125 // Clean up any uploaded files since validation failed
136126 await cleanupUploadedFiles ( processedData ) ;
137127 const errorMessages = validation . errors . map ( err => err . message ) . join ( ', ' ) ;
138128 return sendBadRequest ( res , `Validation failed: ${ errorMessages } ` ) ;
139129 }
140130
141- // Handle resource project specific logic
142- let finalBannerData = _handleResourceProjectBannerLogic ( { ...validation . data } ) ;
143-
144131 // Handle background image specific logic
145- finalBannerData = handleBackgroundImageLogic ( finalBannerData ) ;
132+ const finalBannerData = handleBackgroundImageLogic ( { ... validation . data } ) ;
146133
147134 // Add creator information and system fields
148135 const bannerData = {
@@ -161,9 +148,9 @@ export const createBanner = asyncHandler(async (req: Request, res: Response) =>
161148// Update banner
162149export const updateBanner = asyncHandler ( async ( req : Request , res : Response ) => {
163150 const { id } = req . params ;
164-
151+
165152 const banner = await Banner . findById ( id ) ;
166-
153+
167154 if ( ! banner ) {
168155 return sendNotFound ( res , 'Banner not found' ) ;
169156 }
@@ -173,7 +160,7 @@ export const updateBanner = asyncHandler(async (req: Request, res: Response) =>
173160
174161 // Validate and transform banner data using Zod (final validation after upload)
175162 const validation = validateBanner ( processedData ) ;
176-
163+
177164 if ( ! validation . success ) {
178165 // Clean up any newly uploaded files since validation failed
179166 await cleanupUploadedFiles ( processedData ) ;
@@ -184,17 +171,8 @@ export const updateBanner = asyncHandler(async (req: Request, res: Response) =>
184171 // Store old banner data for file cleanup
185172 const oldBannerData = banner . toObject ( ) ;
186173
187- // Handle template type change from RESOURCE_PROJECT to another type
188- if ( oldBannerData . TemplateType === BannerTemplateType . RESOURCE_PROJECT &&
189- validation ?. data ?. TemplateType !== BannerTemplateType . RESOURCE_PROJECT ) {
190- await handleResourceProjectTemplateChange ( oldBannerData ) ;
191- }
192-
193- // Handle resource project specific logic
194- let finalBannerData = _handleResourceProjectBannerLogic ( { ...validation . data } ) ;
195-
196174 // Handle background image specific logic
197- finalBannerData = handleBackgroundImageLogic ( finalBannerData ) ;
175+ const finalBannerData = handleBackgroundImageLogic ( { ... validation . data } ) ;
198176
199177 // Preserve existing activation date fields and IsActive (not editable in edit form)
200178 finalBannerData . StartDate = banner . StartDate ;
@@ -312,49 +290,6 @@ export const toggleBannerStatus = asyncHandler(async (req: Request, res: Respons
312290 return sendSuccess ( res , updatedBanner , `Banner ${ updatedBanner ?. IsActive ? 'activated' : 'deactivated' } successfully` ) ;
313291} ) ;
314292
315- // Private helper to handle resource project specific logic
316- function _handleResourceProjectBannerLogic ( bannerData : any ) : any {
317- if ( bannerData . TemplateType === BannerTemplateType . RESOURCE_PROJECT && bannerData . ResourceProject ?. ResourceFile ) {
318- const resourceFile = bannerData . ResourceProject . ResourceFile ;
319-
320- // If a new file was uploaded, its URL is in `Url`. We make this the permanent `FileUrl`.
321- if ( resourceFile . Url && ! resourceFile . FileUrl ) {
322- bannerData . ResourceProject . ResourceFile . FileUrl = resourceFile . Url ;
323- }
324-
325- // Update the 'Download' CTA button URL to use the file URL
326- const fileUrl = bannerData . ResourceProject . ResourceFile . FileUrl ;
327- if ( bannerData . CtaButtons && bannerData . CtaButtons . length > 0 && fileUrl ) {
328- const downloadButtonIndex = 0 ;
329- const button = bannerData . CtaButtons [ downloadButtonIndex ] ;
330- if ( button ) {
331- button . Url = fileUrl ;
332- }
333- }
334- }
335- return bannerData ;
336- }
337-
338- // Private helper to handle template type change from RESOURCE_PROJECT
339- // Cleans up resource file and CTA button with blob URL when template type changes
340- async function handleResourceProjectTemplateChange ( oldBannerData : any ) : Promise < void > {
341- // Check if old banner had a resource file with a blob URL
342- if ( oldBannerData . ResourceProject ?. ResourceFile ?. FileUrl ) {
343- const fileUrl = oldBannerData . ResourceProject . ResourceFile . FileUrl ;
344-
345- // Delete the resource file from blob storage if it's a blob URL
346- if ( fileUrl . includes ( 'blob.core.windows.net' ) ) {
347- try {
348- await deleteFile ( fileUrl ) ;
349- console . log ( `Cleaned up resource file during template type change: ${ fileUrl } ` ) ;
350- } catch ( error ) {
351- console . error ( `Failed to delete resource file ${ fileUrl } during template type change:` , error ) ;
352- // Don't throw error - file cleanup failure shouldn't break the update
353- }
354- }
355- }
356- }
357-
358293// Private helper to handle background image logic
359294// Automatically populates Background.Value with BackgroundImage URL when Background.Type is 'image'
360295function handleBackgroundImageLogic ( bannerData : any ) : any {
@@ -381,113 +316,65 @@ function handleBackgroundImageLogic(bannerData: any): any {
381316// Helper function to extract all file URLs from a banner
382317function extractFileUrls ( banner : any ) : string [ ] {
383318 const urls : string [ ] = [ ] ;
384-
319+
385320 // Main media assets
386321 if ( banner . Logo ?. Url ) urls . push ( banner . Logo . Url ) ;
387322 if ( banner . BackgroundImage ?. Url ) urls . push ( banner . BackgroundImage . Url ) ;
388323 if ( banner . MainImage ?. Url ) urls . push ( banner . MainImage . Url ) ;
389-
390- // Partner logos for partnership charter banners (nested structure)
391- if ( banner . PartnershipCharter ?. PartnerLogos && Array . isArray ( banner . PartnershipCharter . PartnerLogos ) ) {
392- banner . PartnershipCharter . PartnerLogos . forEach ( ( logo : any ) => {
393- if ( logo . Url ) urls . push ( logo . Url ) ;
394- } ) ;
395- }
396-
397- // Resource files for resource project banners (nested structure)
398- if ( banner . ResourceProject ?. ResourceFile && banner . ResourceProject . ResourceFile . FileUrl ) {
399- urls . push ( banner . ResourceProject . ResourceFile . FileUrl ) ;
400- }
401-
324+
325+ // Uploaded file (PDFs, images, etc.)
326+ if ( banner . UploadedFile ?. FileUrl ) urls . push ( banner . UploadedFile . FileUrl ) ;
327+
402328 return urls ;
403329}
404330
405331// Helper function to process mixed media fields (existing assets + new files)
406332function processMediaFields ( req : Request ) : any {
407- // Start with the clean, pre-validated data and merge the raw body to get file info
408- // Note: I still not sure if it makes sense to use this merging instead of req.body
409333 const processedData = { ...req . body , ...req . preValidatedData } ;
410-
411- [ 'Logo' , 'BackgroundImage' , 'MainImage' /*, 'AccentGraphic'*/ ] . forEach ( field => {
334+
335+ // Process standard media asset fields (Logo, BackgroundImage, MainImage)
336+ [ 'Logo' , 'BackgroundImage' , 'MainImage' ] . forEach ( field => {
412337 const newFileData = processedData [ `newfile_${ field } ` ] ;
413- const newMetadata = processedData [ `newmetadata_${ field } ` ]
414- ? JSON . parse ( processedData [ `newmetadata_${ field } ` ] )
338+ const newMetadata = processedData [ `newmetadata_${ field } ` ]
339+ ? JSON . parse ( processedData [ `newmetadata_${ field } ` ] )
415340 : null ;
416- const existingMetadata = processedData [ `existing_${ field } ` ]
417- ? JSON . parse ( processedData [ `existing_${ field } ` ] )
341+ const existingMetadata = processedData [ `existing_${ field } ` ]
342+ ? JSON . parse ( processedData [ `existing_${ field } ` ] )
418343 : null ;
419344
420345 let finalAsset = null ;
421346 if ( newFileData ) {
422- // New file uploaded, merge with its metadata
423347 finalAsset = {
424- ...( newMetadata || { } ) , // Contains Position, Opacity, etc.
425- ...newFileData // Contains Url, Filename, Size from upload
348+ ...( newMetadata || { } ) ,
349+ ...newFileData
426350 } ;
427351 } else if ( existingMetadata ) {
428- // No new file, use existing metadata
429352 finalAsset = existingMetadata ;
430353 }
431354
432- // If finalAsset is null (removed by user), set to undefined to satisfy Zod's optional schema
433- // Otherwise, assign the processed asset object.
434- processedData [ field ] = finalAsset ; // === null ? undefined : finalAsset;
355+ processedData [ field ] = finalAsset ;
435356 } ) ;
436357
437-
438-
439- // Process PartnerLogos array field
440- const existingPartnerLogos = processedData . existing_PartnerLogos
441- ? JSON . parse ( processedData . existing_PartnerLogos )
442- : [ ] ;
443- const newPartnerLogos = processedData . newfile_PartnerLogos || [ ] ;
444- const combinedPartnerLogos = [
445- ...existingPartnerLogos ,
446- ...( Array . isArray ( newPartnerLogos ) ? newPartnerLogos : [ newPartnerLogos ] )
447- ] . filter ( Boolean ) ;
448-
449- if ( processedData . PartnershipCharter ) {
450- const partnershipCharter = typeof processedData . PartnershipCharter === 'string'
451- ? JSON . parse ( processedData . PartnershipCharter )
452- : processedData . PartnershipCharter ;
453-
454- partnershipCharter . PartnerLogos = combinedPartnerLogos ;
455- processedData . PartnershipCharter = partnershipCharter ;
456- } else if ( combinedPartnerLogos . length > 0 ) {
457- processedData . PartnershipCharter = { PartnerLogos : combinedPartnerLogos } ;
458- }
459-
460-
461-
462- // Process ResourceFile
463- const newResourceFileData = processedData . newfile_ResourceFile ;
464- const newResourceFileMetadata = processedData . newmetadata_ResourceFile
465- ? JSON . parse ( processedData . newmetadata_ResourceFile )
358+ // Process UploadedFile (general file upload - PDFs, images, etc.)
359+ const newUploadedFileData = processedData . newfile_UploadedFile ;
360+ const newUploadedFileMetadata = processedData . newmetadata_UploadedFile
361+ ? JSON . parse ( processedData . newmetadata_UploadedFile )
466362 : null ;
467- const existingResourceFile = processedData . existing_ResourceFile
468- ? JSON . parse ( processedData . existing_ResourceFile )
363+ const existingUploadedFile = processedData . existing_UploadedFile
364+ ? JSON . parse ( processedData . existing_UploadedFile )
469365 : null ;
470366
471- let finalResourceFile = null ;
472- if ( newResourceFileData ) {
473- finalResourceFile = {
474- ...( newResourceFileMetadata || { } ) ,
475- ...newResourceFileData
367+ let finalUploadedFile = null ;
368+ if ( newUploadedFileData ) {
369+ finalUploadedFile = {
370+ ...( newUploadedFileMetadata || { } ) ,
371+ ...newUploadedFileData
476372 } ;
477- } else if ( existingResourceFile ) {
478- finalResourceFile = existingResourceFile ;
373+ } else if ( existingUploadedFile ) {
374+ finalUploadedFile = existingUploadedFile ;
479375 }
480376
481- if ( processedData . ResourceProject ) {
482- const resourceProject = typeof processedData . ResourceProject === 'string'
483- ? JSON . parse ( processedData . ResourceProject )
484- : processedData . ResourceProject ;
485-
486- resourceProject . ResourceFile = finalResourceFile ;
487- processedData . ResourceProject = resourceProject ;
488- } else if ( finalResourceFile ) {
489- processedData . ResourceProject = { ResourceFile : finalResourceFile } ;
490- }
377+ processedData . UploadedFile = finalUploadedFile ;
491378
492379 // Clean up all temporary form data keys before validation
493380 Object . keys ( processedData ) . forEach ( key => {
0 commit comments