Spaces:
Sleeping
Sleeping
| const AboutUs = require('../models/AboutUs'); | |
| const { saveOptimizedImage, removeStoredFile } = require('../services/mediaService'); | |
| const { asyncController } = require('../utils/asyncController'); | |
| const stripHtml = (value) => String(value || '').replace(/<[^>]*>/g, ''); | |
| const normalizeStringArray = (value) => { | |
| if (Array.isArray(value)) { | |
| return value.map((item) => String(item).trim()).filter(Boolean); | |
| } | |
| if (value === undefined || value === null) { | |
| return []; | |
| } | |
| const raw = String(value).trim(); | |
| if (!raw) return []; | |
| try { | |
| const parsed = JSON.parse(raw); | |
| if (Array.isArray(parsed)) { | |
| return parsed.map((item) => String(item).trim()).filter(Boolean); | |
| } | |
| } catch { | |
| // Fallback to single-value handling when value is not JSON. | |
| } | |
| return [raw]; | |
| }; | |
| const normalizeRooms = (value) => { | |
| if (value === undefined || value === null) { | |
| return []; | |
| } | |
| let parsed = value; | |
| if (!Array.isArray(parsed)) { | |
| const raw = String(value).trim(); | |
| if (!raw) return []; | |
| try { | |
| parsed = JSON.parse(raw); | |
| } catch { | |
| parsed = []; | |
| } | |
| } | |
| if (!Array.isArray(parsed)) return []; | |
| return parsed.map((item) => { | |
| if (!item || typeof item !== 'object') { | |
| return { | |
| title: String(item || '').trim(), | |
| image: '', | |
| }; | |
| } | |
| return { | |
| title: String(item.title || '').trim(), | |
| image: String(item.image || '').trim(), | |
| }; | |
| }); | |
| }; | |
| const mapUploadedFilesByField = (req) => { | |
| const filesByField = {}; | |
| if (Array.isArray(req.files)) { | |
| req.files.forEach((file) => { | |
| if (!file?.fieldname) return; | |
| if (!filesByField[file.fieldname]) filesByField[file.fieldname] = []; | |
| filesByField[file.fieldname].push(file); | |
| }); | |
| } else if (req.files && typeof req.files === 'object') { | |
| Object.entries(req.files).forEach(([field, files]) => { | |
| if (!Array.isArray(files)) return; | |
| filesByField[field] = files; | |
| }); | |
| } | |
| if (req.file?.fieldname) { | |
| filesByField[req.file.fieldname] = [req.file]; | |
| } | |
| return filesByField; | |
| }; | |
| // GET /api/about-us — public | |
| const getAboutUs = asyncController(async (req, res) => { | |
| const doc = await AboutUs.findOne({ slug: 'main' }).lean(); | |
| if (!doc) { | |
| return res.json({ | |
| greeting: '', | |
| vision: '', | |
| mission: [], | |
| objectives: [], | |
| studyProgramObjectives: '', | |
| scientificVision: [], | |
| headPhoto: '', | |
| locationText: '', | |
| locationPhoto: '', | |
| mapsEmbedUrl: '', | |
| programs: [], | |
| rooms: [], | |
| }); | |
| } | |
| return res.json({ | |
| greeting: doc.greeting || '', | |
| vision: doc.vision || '', | |
| mission: Array.isArray(doc.mission) ? doc.mission : [], | |
| objectives: Array.isArray(doc.objectives) ? doc.objectives : [], | |
| studyProgramObjectives: doc.studyProgramObjectives || '', | |
| scientificVision: Array.isArray(doc.scientificVision) ? doc.scientificVision : [], | |
| headPhoto: doc.headPhoto || '', | |
| locationText: doc.locationText || '', | |
| locationPhoto: doc.locationPhoto || '', | |
| mapsEmbedUrl: doc.mapsEmbedUrl || '', | |
| programs: Array.isArray(doc.programs) ? doc.programs : [], | |
| rooms: Array.isArray(doc.rooms) | |
| ? doc.rooms | |
| .map((room) => ({ | |
| title: String(room?.title || '').trim(), | |
| image: String(room?.image || '').trim(), | |
| })) | |
| .filter((room) => room.title || room.image) | |
| : [], | |
| }); | |
| }); | |
| // PUT /api/about-us — admin only | |
| const updateAboutUs = asyncController(async (req, res) => { | |
| const { greeting, vision, mission, objectives, studyProgramObjectives, scientificVision, locationText, mapsEmbedUrl, programs, rooms } = req.body; | |
| const currentDoc = await AboutUs.findOne({ slug: 'main' }).lean(); | |
| const filesByField = mapUploadedFilesByField(req); | |
| const headPhotoFile = filesByField.headPhoto?.[0]; | |
| const locationPhotoFile = filesByField.locationPhoto?.[0]; | |
| const payload = {}; | |
| if (greeting !== undefined) payload.greeting = stripHtml(String(greeting).trim()); | |
| if (vision !== undefined) payload.vision = stripHtml(String(vision).trim()); | |
| if (mission !== undefined) { | |
| payload.mission = normalizeStringArray(mission).map(stripHtml); | |
| } | |
| if (objectives !== undefined) { | |
| payload.objectives = normalizeStringArray(objectives).map(stripHtml); | |
| } | |
| if (studyProgramObjectives !== undefined) { | |
| payload.studyProgramObjectives = stripHtml(String(studyProgramObjectives).trim()); | |
| } | |
| if (scientificVision !== undefined) { | |
| payload.scientificVision = normalizeStringArray(scientificVision).map(stripHtml); | |
| } | |
| if (locationText !== undefined) { | |
| payload.locationText = stripHtml(String(locationText).trim()); | |
| } | |
| if (mapsEmbedUrl !== undefined) { | |
| let rawUrl = String(mapsEmbedUrl).trim(); | |
| // If user accidentally pasted a full <iframe> snippet, extract only the src URL. | |
| const srcMatch = rawUrl.match(/\bsrc=["']([^"']+)["']/); | |
| if (srcMatch) rawUrl = srcMatch[1].trim(); | |
| // Strip any trailing " and HTML attribute fragments left after src="..." extraction. | |
| rawUrl = rawUrl.replace(/^["']|["'].*$/g, '').trim(); | |
| const isValid = !rawUrl || rawUrl.startsWith('https://www.google.com/maps') || rawUrl.startsWith('https://maps.google.com'); | |
| payload.mapsEmbedUrl = isValid ? rawUrl : ''; | |
| } | |
| if (programs !== undefined) { | |
| payload.programs = normalizeStringArray(programs).map(stripHtml); | |
| } | |
| if (rooms !== undefined) { | |
| payload.rooms = normalizeRooms(rooms); | |
| } | |
| if (headPhotoFile) { | |
| const nextHeadPhoto = await saveOptimizedImage({ | |
| file: headPhotoFile, | |
| subdir: 'about-us', | |
| width: 1200, | |
| height: 1400, | |
| quality: 80, | |
| }); | |
| if (nextHeadPhoto) { | |
| payload.headPhoto = nextHeadPhoto; | |
| await removeStoredFile(currentDoc?.headPhoto); | |
| } | |
| } | |
| if (locationPhotoFile) { | |
| const nextLocationPhoto = await saveOptimizedImage({ | |
| file: locationPhotoFile, | |
| subdir: 'about-us/location', | |
| width: 1200, | |
| height: 1400, | |
| quality: 80, | |
| }); | |
| if (nextLocationPhoto) { | |
| payload.locationPhoto = nextLocationPhoto; | |
| await removeStoredFile(currentDoc?.locationPhoto); | |
| } | |
| } | |
| if (payload.rooms !== undefined) { | |
| const nextRooms = []; | |
| const roomImagesFromStableField = filesByField.roomImages || []; | |
| for (let index = 0; index < payload.rooms.length; index += 1) { | |
| const room = payload.rooms[index]; | |
| const roomImageFile = roomImagesFromStableField[index] || filesByField[`roomImage_${index}`]?.[0]; | |
| let nextImage = room.image; | |
| if (roomImageFile) { | |
| nextImage = await saveOptimizedImage({ | |
| file: roomImageFile, | |
| subdir: 'about-us/rooms', | |
| width: 900, | |
| height: 900, | |
| quality: 80, | |
| }); | |
| } | |
| if (!room.title && !nextImage) continue; | |
| nextRooms.push({ title: stripHtml(room.title), image: nextImage }); | |
| } | |
| payload.rooms = nextRooms; | |
| const previousRoomImages = Array.isArray(currentDoc?.rooms) | |
| ? currentDoc.rooms.map((room) => String(room?.image || '').trim()).filter(Boolean) | |
| : []; | |
| const nextRoomImages = payload.rooms.map((room) => String(room?.image || '').trim()).filter(Boolean); | |
| const nextRoomImageSet = new Set(nextRoomImages); | |
| for (const oldPath of previousRoomImages) { | |
| if (!nextRoomImageSet.has(oldPath)) { | |
| await removeStoredFile(oldPath); | |
| } | |
| } | |
| } | |
| if (!Object.keys(payload).length) { | |
| return res.status(400).json({ message: 'No fields provided to update' }); | |
| } | |
| const doc = await AboutUs.findOneAndUpdate( | |
| { slug: 'main' }, | |
| { $set: payload }, | |
| { returnDocument: 'after', upsert: true, runValidators: true } | |
| ); | |
| res.json(doc); | |
| }, { defaultStatus: 400 }); | |
| module.exports = { getAboutUs, updateAboutUs }; | |