Skip to content

Commit 56b9f4d

Browse files
committed
Finish implementing swep banners
1 parent 2de8cd6 commit 56b9f4d

File tree

9 files changed

+121
-113
lines changed

9 files changed

+121
-113
lines changed

src/controllers/bannerController.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,8 @@ export const deleteBanner = asyncHandler(async (req: Request, res: Response) =>
213213
}
214214

215215
// Extract all file URLs from the banner before deletion
216-
const fileUrls = extractFileUrls(banner.toObject());
216+
// banner is already a plain object from .lean(), no need for .toObject()
217+
const fileUrls = extractFileUrls(banner);
217218

218219
// Delete the banner from database first
219220
await Banner.findByIdAndDelete(id);

src/controllers/swepBannerController.ts

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ export const getSwepBanners = asyncHandler(async (req: Request, res: Response) =
2727
if (search && typeof search === 'string') {
2828
conditions.push({
2929
$or: [
30-
{ title: { $regex: search.trim(), $options: 'i' } },
31-
{ shortMessage: { $regex: search.trim(), $options: 'i' } },
32-
{ body: { $regex: search.trim(), $options: 'i' } }
30+
{ Title: { $regex: search.trim(), $options: 'i' } },
31+
{ ShortMessage: { $regex: search.trim(), $options: 'i' } },
32+
{ Body: { $regex: search.trim(), $options: 'i' } }
3333
]
3434
});
3535
}
@@ -38,15 +38,15 @@ export const getSwepBanners = asyncHandler(async (req: Request, res: Response) =
3838
if (locations && typeof locations === 'string') {
3939
const locationArray = locations.split(',').map(loc => loc.trim()).filter(Boolean);
4040
if (locationArray.length > 0) {
41-
conditions.push({ locationSlug: { $in: locationArray } });
41+
conditions.push({ LocationSlug: { $in: locationArray } });
4242
}
4343
} else if (location && typeof location === 'string') {
44-
conditions.push({ locationSlug: location });
44+
conditions.push({ LocationSlug: location });
4545
}
4646

4747
// Apply isActive filter
4848
if (isActive !== undefined && isActive !== 'undefined') {
49-
conditions.push({ isActive: isActive === 'true' });
49+
conditions.push({ IsActive: isActive === 'true' });
5050
}
5151

5252
// Combine all conditions with AND logic
@@ -82,7 +82,7 @@ export const getSwepBanners = asyncHandler(async (req: Request, res: Response) =
8282
// @route GET /api/swep-banners/:location
8383
// @access Private
8484
export const getSwepBannerByLocation = asyncHandler(async (req: Request, res: Response) => {
85-
const swepBanner = await SwepBanner.findOne({ locationSlug: req.params.location });
85+
const swepBanner = await SwepBanner.findOne({ LocationSlug: req.params.location });
8686

8787
if (!swepBanner) {
8888
return sendNotFound(res, 'SWEP banner not found for this location');
@@ -98,18 +98,18 @@ export const updateSwepBanner = asyncHandler(async (req: Request, res: Response)
9898
const { location } = req.params;
9999

100100
// Get existing SWEP banner
101-
const existingSwep = await SwepBanner.findOne({ locationSlug: location }).lean();
101+
const existingSwep = await SwepBanner.findOne({ LocationSlug: location }).lean();
102102
if (!existingSwep) {
103103
return sendNotFound(res, 'SWEP banner not found for this location');
104104
}
105105

106106
// Process media fields (existing assets + new uploads) using Banner approach
107107
const processedData = processSwepMediaFields(req);
108108

109-
// Preserve existing date fields and isActive (not editable in edit form)
110-
processedData.swepActiveFrom = existingSwep.swepActiveFrom;
111-
processedData.swepActiveUntil = existingSwep.swepActiveUntil;
112-
processedData.isActive = existingSwep.isActive;
109+
// Preserve existing date fields and IsActive (not editable in edit form)
110+
processedData.SwepActiveFrom = existingSwep.SwepActiveFrom;
111+
processedData.SwepActiveUntil = existingSwep.SwepActiveUntil;
112+
processedData.IsActive = existingSwep.IsActive;
113113

114114
// Validate the processed data
115115
const validation = validateSwepBanner(processedData);
@@ -122,21 +122,26 @@ export const updateSwepBanner = asyncHandler(async (req: Request, res: Response)
122122
}
123123

124124
// Store old banner data for file cleanup
125-
const oldImage = existingSwep.image;
125+
const oldImage = existingSwep.Image;
126126

127127
// Update SWEP banner
128128
const updatedSwep = await SwepBanner.findOneAndUpdate(
129-
{ locationSlug: location },
129+
{ LocationSlug: location },
130130
{
131131
...validation.data,
132+
Image: validation.data?.Image || null,
132133
DocumentModifiedDate: new Date()
133134
},
134135
{ new: true, runValidators: true }
135136
);
136137

137-
// Clean up old image if it was replaced
138-
if (updatedSwep && oldImage && oldImage !== updatedSwep.image) {
139-
await cleanupSwepUnusedFiles(oldImage);
138+
// Clean up old image if it was replaced or removed (empty Image property indicates removal)
139+
if (oldImage) {
140+
if (!updatedSwep?.Image || updatedSwep.Image === null || oldImage !== updatedSwep.Image) {
141+
// Image was removed (empty) or replaced with a new one - delete old image
142+
await cleanupSwepUnusedFiles(oldImage);
143+
console.log(`Cleaned up old SWEP image: ${oldImage}`);
144+
}
140145
}
141146

142147
return sendSuccess(res, updatedSwep);
@@ -147,23 +152,23 @@ export const updateSwepBanner = asyncHandler(async (req: Request, res: Response)
147152
// @access Private
148153
export const toggleSwepBannerActive = asyncHandler(async (req: Request, res: Response) => {
149154
const { location } = req.params;
150-
const { isActive, swepActiveFrom, swepActiveUntil } = req.body;
155+
const { IsActive, SwepActiveFrom, SwepActiveUntil } = req.body;
151156

152157
// Get existing SWEP banner
153-
const existingSwep = await SwepBanner.findOne({ locationSlug: location });
158+
const existingSwep = await SwepBanner.findOne({ LocationSlug: location });
154159
if (!existingSwep) {
155160
return sendNotFound(res, 'SWEP banner not found for this location');
156161
}
157162

158163
// Prepare update data
159-
let shouldActivateNow = isActive !== undefined ? isActive : !existingSwep.isActive;
164+
let shouldActivateNow = IsActive !== undefined ? IsActive : !existingSwep.IsActive;
160165

161166
// Check if scheduled start date equals today - if so, activate immediately
162-
if (swepActiveFrom !== undefined && swepActiveFrom !== null) {
167+
if (SwepActiveFrom !== undefined && SwepActiveFrom !== null) {
163168
const today = new Date();
164169
today.setHours(0, 0, 0, 0);
165170

166-
const activeFromDate = new Date(swepActiveFrom);
171+
const activeFromDate = new Date(SwepActiveFrom);
167172
activeFromDate.setHours(0, 0, 0, 0);
168173

169174
// If start date is today, activate immediately
@@ -173,28 +178,28 @@ export const toggleSwepBannerActive = asyncHandler(async (req: Request, res: Res
173178
}
174179

175180
const updateData: any = {
176-
isActive: shouldActivateNow,
181+
IsActive: shouldActivateNow,
177182
DocumentModifiedDate: new Date()
178183
};
179184

180185
// Handle date range for scheduled activation
181-
if (swepActiveFrom !== undefined && swepActiveFrom !== null) {
182-
updateData.swepActiveFrom = new Date(swepActiveFrom);
183-
} else if (updateData.isActive && !existingSwep.swepActiveFrom) {
184-
// If activating immediately without dates, set swepActiveFrom to now
185-
updateData.swepActiveFrom = new Date();
186+
if (SwepActiveFrom !== undefined && SwepActiveFrom !== null) {
187+
updateData.SwepActiveFrom = new Date(SwepActiveFrom);
188+
} else if (updateData.IsActive && !existingSwep.SwepActiveFrom) {
189+
// If activating immediately without dates, set SwepActiveFrom to now
190+
updateData.SwepActiveFrom = new Date();
186191
}
187192

188-
if (swepActiveUntil !== undefined && swepActiveUntil !== null) {
189-
updateData.swepActiveUntil = new Date(swepActiveUntil);
190-
} else if (!updateData.isActive && !swepActiveUntil) {
191-
// If deactivating without explicit date, set swepActiveUntil to now
192-
updateData.swepActiveUntil = new Date();
193+
if (SwepActiveUntil !== undefined && SwepActiveUntil !== null) {
194+
updateData.SwepActiveUntil = new Date(SwepActiveUntil);
195+
} else if (!updateData.IsActive && !SwepActiveUntil) {
196+
// If deactivating without explicit date, set SwepActiveUntil to now
197+
updateData.SwepActiveUntil = new Date();
193198
}
194199

195200
// Update SWEP banner
196201
const updatedSwep = await SwepBanner.findOneAndUpdate(
197-
{ locationSlug: location },
202+
{ LocationSlug: location },
198203
updateData,
199204
{ new: true, runValidators: true }
200205
);
@@ -215,16 +220,20 @@ function processSwepMediaFields(req: Request): any {
215220
const existingData = processedData.existing_image
216221
? JSON.parse(processedData.existing_image)
217222
: null;
223+
const explicitImageValue = processedData.Image; // Check for explicit Image field
218224

219225
if (newFileData) {
220226
// New file uploaded - uploadMiddleware attaches asset with Url property
221-
processedData.image = newFileData.Url || newFileData.url;
227+
processedData.Image = newFileData.Url || newFileData.url;
222228
} else if (existingData) {
223229
// No new file, preserve existing image URL
224-
processedData.image = existingData.url || existingData.Url;
230+
processedData.Image = existingData.url || existingData.Url;
231+
} else if (explicitImageValue === '') {
232+
// User explicitly removed the image by sending empty string
233+
processedData.Image = '';
225234
} else {
226-
// Image removed by user
227-
processedData.image = undefined;
235+
// Image removed by user (no explicit value, no new file, no existing data)
236+
processedData.Image = '';
228237
}
229238

230239
// Clean up temporary form data keys
@@ -236,12 +245,12 @@ function processSwepMediaFields(req: Request): any {
236245

237246
// Helper function to clean up uploaded files when validation fails
238247
async function cleanupSwepUploadedFiles(processedData: any): Promise<void> {
239-
if (processedData.image) {
248+
if (processedData.Image && processedData.Image !== '') {
240249
try {
241-
await deleteFile(processedData.image);
242-
console.log(`Cleaned up uploaded SWEP image after validation failure: ${processedData.image}`);
250+
await deleteFile(processedData.Image);
251+
console.log(`Cleaned up uploaded SWEP image after validation failure: ${processedData.Image}`);
243252
} catch (error) {
244-
console.error(`Failed to delete uploaded SWEP image ${processedData.image}:`, error);
253+
console.error(`Failed to delete uploaded SWEP image ${processedData.Image}:`, error);
245254
// Don't throw - file cleanup failure shouldn't break the response
246255
}
247256
}

src/jobs/swepActivationJob.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,32 +38,32 @@ export function startSwepActivationJob() {
3838
let needsUpdate = false;
3939

4040
// Check if banner should be activated today
41-
if (swep.swepActiveFrom) {
42-
const activeFromDate = new Date(swep.swepActiveFrom);
41+
if (swep.SwepActiveFrom) {
42+
const activeFromDate = new Date(swep.SwepActiveFrom);
4343
activeFromDate.setUTCHours(0, 0, 0, 0); // Use UTC for consistent comparison
4444

4545
// If today equals or is after the activeFrom date and banner is not active
46-
if (activeFromDate.getTime() === today.getTime() && !swep.isActive) {
47-
updateData.isActive = true;
46+
if (activeFromDate.getTime() === today.getTime() && !swep.IsActive) {
47+
updateData.IsActive = true;
4848
needsUpdate = true;
4949
activatedCount++;
50-
console.log(`SWEP banner activated: ${swep.locationName} (${swep.locationSlug})`);
51-
console.log(` - Active from: ${swep.swepActiveFrom.toISOString()}`);
50+
console.log(`SWEP banner activated: ${swep.LocationName} (${swep.LocationSlug})`);
51+
console.log(` - Active from: ${swep.SwepActiveFrom.toISOString()}`);
5252
}
5353
}
5454

5555
// Check if banner should be deactivated today
56-
if (swep.swepActiveUntil) {
57-
const activeUntilDate = new Date(swep.swepActiveUntil);
56+
if (swep.SwepActiveUntil) {
57+
const activeUntilDate = new Date(swep.SwepActiveUntil);
5858
activeUntilDate.setUTCHours(0, 0, 0, 0); // Use UTC for consistent comparison
5959

6060
// If today equals or is after the activeUntil date and banner is active
61-
if (activeUntilDate.getTime() === today.getTime() && swep.isActive) {
62-
updateData.isActive = false;
61+
if (activeUntilDate.getTime() === today.getTime() && swep.IsActive) {
62+
updateData.IsActive = false;
6363
needsUpdate = true;
6464
deactivatedCount++;
65-
console.log(`SWEP banner deactivated: ${swep.locationName} (${swep.locationSlug})`);
66-
console.log(` - Active until: ${swep.swepActiveUntil.toISOString()}`);
65+
console.log(`SWEP banner deactivated: ${swep.LocationName} (${swep.LocationSlug})`);
66+
console.log(` - Active until: ${swep.SwepActiveUntil.toISOString()}`);
6767
}
6868
}
6969

@@ -76,7 +76,7 @@ export function startSwepActivationJob() {
7676
);
7777
}
7878
} catch (error) {
79-
const errorMsg = `Error updating ${swep.locationName}: ${error instanceof Error ? error.message : 'Unknown error'}`;
79+
const errorMsg = `Error updating ${swep.LocationName}: ${error instanceof Error ? error.message : 'Unknown error'}`;
8080
console.error(errorMsg);
8181
errors.push(errorMsg);
8282
}
@@ -130,12 +130,12 @@ export async function runSwepActivationCheckNow() {
130130

131131
for (const swep of swepBanners) {
132132
// Check activation date
133-
if (swep.swepActiveFrom) {
134-
const activeFromDate = new Date(swep.swepActiveFrom);
133+
if (swep.SwepActiveFrom) {
134+
const activeFromDate = new Date(swep.SwepActiveFrom);
135135
activeFromDate.setUTCHours(0, 0, 0, 0);
136136

137137
if (activeFromDate.getTime() === today.getTime()) {
138-
if (!swep.isActive) {
138+
if (!swep.IsActive) {
139139
stats.needsActivation++;
140140
} else {
141141
stats.alreadyActive++;
@@ -144,12 +144,12 @@ export async function runSwepActivationCheckNow() {
144144
}
145145

146146
// Check deactivation date
147-
if (swep.swepActiveUntil) {
148-
const activeUntilDate = new Date(swep.swepActiveUntil);
147+
if (swep.SwepActiveUntil) {
148+
const activeUntilDate = new Date(swep.SwepActiveUntil);
149149
activeUntilDate.setUTCHours(0, 0, 0, 0);
150150

151151
if (activeUntilDate.getTime() === today.getTime()) {
152-
if (swep.isActive) {
152+
if (swep.IsActive) {
153153
stats.needsDeactivation++;
154154
} else {
155155
stats.alreadyInactive++;

src/middleware/authMiddleware.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,10 +1488,10 @@ export const requireSwepBannerAccess = asyncHandler(async (req: Request, res: Re
14881488

14891489
if (swepBannerLocation) {
14901490
try{
1491-
const banner = await SwepBanner.findOne({ locationSlug: swepBannerLocation }).lean();
1491+
const banner = await SwepBanner.findOne({ LocationSlug: swepBannerLocation }).lean();
14921492

14931493
// For location-based access, check the LocationSlug
1494-
const locations = (banner?.locationSlug || '').split(',').map(l => l.trim()).filter(Boolean);
1494+
const locations = (banner?.LocationSlug || '').split(',').map(l => l.trim()).filter(Boolean);
14951495

14961496
if (validateSwepAndCityAdminLocationsAccess(userAuthClaims, locations, res)) {
14971497
return; // Access denied, response already sent

src/models/swepModel.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,50 +19,50 @@ export const SwepBannerSchema = new Schema<ISwepBanner>({
1919
},
2020

2121
// Core fields
22-
locationSlug: {
22+
LocationSlug: {
2323
type: String,
2424
required: true,
2525
index: true,
2626
unique: true
2727
},
28-
locationName: {
28+
LocationName: {
2929
type: String,
3030
required: true
3131
},
32-
title: {
32+
Title: {
3333
type: String,
3434
required: true
3535
},
36-
body: {
36+
Body: {
3737
type: String,
3838
required: true
3939
},
40-
image: {
40+
Image: {
4141
type: String,
42-
required: true
42+
required: false
4343
},
44-
shortMessage: {
44+
ShortMessage: {
4545
type: String,
4646
required: true
4747
},
4848

4949
// Date fields
50-
swepActiveFrom: {
50+
SwepActiveFrom: {
5151
type: Date,
5252
required: false
5353
},
54-
swepActiveUntil: {
54+
SwepActiveUntil: {
5555
type: Date,
5656
required: false
5757
},
58-
isActive: {
58+
IsActive: {
5959
type: Boolean,
6060
default: false,
6161
required: true
6262
},
6363

6464
// Emergency contact
65-
emergencyContact: {
65+
EmergencyContact: {
6666
type: EmergencyContactSchema,
6767
required: false
6868
}
@@ -72,9 +72,9 @@ export const SwepBannerSchema = new Schema<ISwepBanner>({
7272
});
7373

7474
// Indexes for performance
75-
SwepBannerSchema.index({ locationSlug: 1, isActive: 1 });
76-
SwepBannerSchema.index({ isActive: 1 });
77-
SwepBannerSchema.index({ swepActiveFrom: 1, swepActiveUntil: 1 });
75+
SwepBannerSchema.index({ LocationSlug: 1, IsActive: 1 });
76+
SwepBannerSchema.index({ IsActive: 1 });
77+
SwepBannerSchema.index({ SwepActiveFrom: 1, SwepActiveUntil: 1 });
7878

7979
// Create and export the model
8080
export const SwepBanner = model<ISwepBanner>('SwepBanner', SwepBannerSchema);

0 commit comments

Comments
 (0)