'use strict'; const mdl_AchievementDefinitions = require('../../models/users/achievement_definitions.mdl'); const CourseAchievement = require('../../models/courses/course_achievement.mdl'); const R = require('../../utils/response.util'); const logActivity = require('../../utils/logActivity.util'); const VALID_TYPES = ['badge', 'milestone']; // ─── GET /admin/achievements ────────────────────────────────────────────────── exports.getAchievements = async (req, res) => { try { const achievements = await mdl_AchievementDefinitions.findAll({ order: [['createdAt', 'ASC']], }); return R.success(res, 'Achievements retrieved.', achievements); } catch (err) { console.error('[ADMIN][GET ACHIEVEMENTS]', err); return R.error(res, 'Could not retrieve achievements.', 500); } }; // ─── GET /admin/achievements/:id ────────────────────────────────────────────── exports.getAchievement = async (req, res) => { try { const achievement = await mdl_AchievementDefinitions.findByPk(req.params.id); if (!achievement) return R.error(res, 'Achievement not found.', 404); return R.success(res, 'Achievement retrieved.', achievement); } catch (err) { console.error('[ADMIN][GET ACHIEVEMENT]', err); return R.error(res, 'Could not retrieve achievement.', 500); } }; // ─── POST /admin/achievements ───────────────────────────────────────────────── exports.createAchievement = async (req, res) => { try { const { key, type, label, description, icon, trigger, is_active } = req.body; if (!key || !label) return R.error(res, 'key and label are required.', 400); if (type && !VALID_TYPES.includes(type)) return R.error(res, `type must be one of: ${VALID_TYPES.join(', ')}.`, 400); const exists = await mdl_AchievementDefinitions.findOne({ where: { key } }); if (exists) return R.error(res, `An achievement with key "${key}" already exists.`, 409); const achievement = await mdl_AchievementDefinitions.create({ key, type: type || 'badge', label, description: description ?? null, icon: icon || null, trigger: trigger || null, is_active: is_active !== undefined ? !!is_active : true, is_system: false, // only seed data may be system-protected }); logActivity(req.user?.user_id, 'create_achievement', { entityType: 'achievement', details: { key, label } }); return R.success(res, 'Achievement created.', achievement, 201); } catch (err) { console.error('[ADMIN][CREATE ACHIEVEMENT]', err); return R.error(res, 'Could not create achievement.', 500); } }; // ─── PUT /admin/achievements/:id ────────────────────────────────────────────── exports.updateAchievement = async (req, res) => { try { const achievement = await mdl_AchievementDefinitions.findByPk(req.params.id); if (!achievement) return R.error(res, 'Achievement not found.', 404); const { key, type, label, description, icon, trigger, is_active } = req.body; if (achievement.is_system && key !== undefined && key !== achievement.key) return R.error(res, 'The key of a system achievement cannot be changed.', 400); if (achievement.is_system && type !== undefined && type !== achievement.type) return R.error(res, 'The type of a system achievement cannot be changed.', 400); if (type && !VALID_TYPES.includes(type)) return R.error(res, `type must be one of: ${VALID_TYPES.join(', ')}.`, 400); if (!achievement.is_system && key !== undefined && key !== achievement.key) { const exists = await mdl_AchievementDefinitions.findOne({ where: { key } }); if (exists) return R.error(res, `An achievement with key "${key}" already exists.`, 409); } await achievement.update({ key: (!achievement.is_system && key !== undefined) ? key : achievement.key, type: (!achievement.is_system && type !== undefined) ? type : achievement.type, label: label ?? achievement.label, description: description !== undefined ? (description || null) : achievement.description, icon: icon !== undefined ? (icon || null) : achievement.icon, trigger: trigger !== undefined ? (trigger || null) : achievement.trigger, is_active: is_active !== undefined ? !!is_active : achievement.is_active, }); logActivity(req.user?.user_id, 'update_achievement', { entityType: 'achievement', details: { id: achievement.achievement_definition_id, key: achievement.key } }); return R.success(res, 'Achievement updated.', achievement); } catch (err) { console.error('[ADMIN][UPDATE ACHIEVEMENT]', err); return R.error(res, 'Could not update achievement.', 500); } }; // ─── DELETE /admin/achievements/:id ─────────────────────────────────────────── exports.deleteAchievement = async (req, res) => { try { const achievement = await mdl_AchievementDefinitions.findByPk(req.params.id); if (!achievement) return R.error(res, 'Achievement not found.', 404); if (achievement.is_system) return R.error(res, 'Built-in system achievements cannot be deleted.', 400); const assignedCourses = await CourseAchievement.count({ where: { achievement_key: achievement.key } }); if (assignedCourses > 0) return R.error(res, `Cannot delete — ${assignedCourses} course(s) still reference this achievement. Unassign it first.`, 409); await achievement.destroy(); logActivity(req.user?.user_id, 'delete_achievement', { entityType: 'achievement', details: { key: achievement.key } }); return R.success(res, 'Achievement deleted.'); } catch (err) { console.error('[ADMIN][DELETE ACHIEVEMENT]', err); return R.error(res, 'Could not delete achievement.', 500); } };