chore: relocate backend into apps/api ahead of monorepo merge

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 12:20:40 +08:00
co-authored by Claude Sonnet 5
parent af44598df6
commit eaa6d2276a
388 changed files with 0 additions and 13998 deletions
@@ -0,0 +1,126 @@
'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);
}
};