tinfo-web-server / controllers /aboutUsController.js
Faridaqurr
initial backend
e8c33fa
Raw
History Blame Contribute Delete
7.79 kB
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 };