mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
220 lines
9.9 KiB
JavaScript
220 lines
9.9 KiB
JavaScript
'use strict';
|
|
const { Op } = require('sequelize');
|
|
const mdl_Category = require('../../models/courses/categories.mdl');
|
|
const mdl_Users = require('../../models/users/users.mdl');
|
|
const { CourseProductCategory } = require('../../models/courses/courses.mdl');
|
|
const R = require('../../utils/response.util');
|
|
const logActivity = require('../../utils/logActivity.util');
|
|
const { paginate } = require('../../utils/paginate.util');
|
|
const {
|
|
excludeAttributes: categoriesExclude,
|
|
jsonbSchemas: categoriesSchemas,
|
|
} = require('../../models/courses/categories.attributes');
|
|
|
|
const slugify = (str) =>
|
|
str.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
|
|
|
|
exports.getCategories = async (req, res) => {
|
|
try {
|
|
const archived = req.query.archived === 'true';
|
|
const result = await paginate(mdl_Category, req, {
|
|
excludeAttributes: categoriesExclude,
|
|
jsonbSchemas: categoriesSchemas,
|
|
context: archived ? 'archived' : 'list',
|
|
auditOptions: { mdl_Users, parentAlias: 'Category' },
|
|
findOptions: archived
|
|
? { paranoid: false, where: { deletedAt: { [Op.ne]: null } } }
|
|
: {},
|
|
});
|
|
return R.success(res, 'Categories retrieved.', result);
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][GET ALL]', err);
|
|
return R.error(res, 'Could not retrieve categories.', 500);
|
|
}
|
|
};
|
|
|
|
exports.getCategory = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id, { paranoid: false });
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
return R.success(res, 'Category retrieved.', row);
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][GET ONE]', err);
|
|
return R.error(res, 'Could not retrieve category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.createCategory = async (req, res) => {
|
|
try {
|
|
const { name, description, is_active } = req.body;
|
|
if (!name) return R.error(res, 'name is required.', 400);
|
|
|
|
const slug = slugify(name);
|
|
const row = await mdl_Category.create({
|
|
name, slug, description: description ?? null, is_active: is_active ?? true,
|
|
createdBy: req.body.createdBy ?? req.user?.user_id ?? null,
|
|
});
|
|
logActivity(req.user?.user_id, 'create_category', { entityType: 'category', entityId: row.category_id, details: { name: row.name } });
|
|
return R.success(res, 'Category created.', row, 201);
|
|
} catch (err) {
|
|
if (err.name === 'SequelizeUniqueConstraintError')
|
|
return R.error(res, 'A category with that name already exists.', 409);
|
|
console.error('[ADMIN][CATEGORIES][CREATE]', err);
|
|
return R.error(res, 'Could not create category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.updateCategory = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id);
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
|
|
const { name, description, is_active } = req.body;
|
|
const slug = name ? slugify(name) : row.slug;
|
|
await row.update({
|
|
name: name ?? row.name, slug, description: description ?? row.description, is_active: is_active ?? row.is_active,
|
|
updatedBy: req.body.updatedBy ?? req.user?.user_id ?? null,
|
|
});
|
|
logActivity(req.user?.user_id, 'update_category', { entityType: 'category', entityId: row.category_id });
|
|
return R.success(res, 'Category updated.', row);
|
|
} catch (err) {
|
|
if (err.name === 'SequelizeUniqueConstraintError')
|
|
return R.error(res, 'A category with that name already exists.', 409);
|
|
console.error('[ADMIN][CATEGORIES][UPDATE]', err);
|
|
return R.error(res, 'Could not update category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.archiveCategory = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id);
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
await row.update({ deletedBy: req.user?.user_id ?? null, is_active: false });
|
|
await row.destroy();
|
|
logActivity(req.user?.user_id, 'archive_category', { entityType: 'category', entityId: row.category_id });
|
|
return R.success(res, 'Category archived.');
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][ARCHIVE]', err);
|
|
return R.error(res, 'Could not archive category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.restoreCategory = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id, { paranoid: false });
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
if (!row.deletedAt) return R.error(res, 'Category is not archived.', 400);
|
|
await row.restore();
|
|
await row.update({ deletedBy: null, is_active: true });
|
|
logActivity(req.user?.user_id, 'restore_category', { entityType: 'category', entityId: row.category_id });
|
|
return R.success(res, 'Category restored.', row);
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][RESTORE]', err);
|
|
return R.error(res, 'Could not restore category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.bulkArchiveCategories = async (req, res) => {
|
|
try {
|
|
const { ids } = req.body;
|
|
if (!Array.isArray(ids) || !ids.length) return R.error(res, 'No category IDs provided.', 400);
|
|
|
|
const rows = await mdl_Category.findAll({ where: { id: ids } });
|
|
if (!rows.length) return R.error(res, 'No categories found.', 404);
|
|
|
|
const activeIds = rows.map((r) => r.id);
|
|
await Promise.all(rows.map((r) => r.update({ deletedBy: req.user?.user_id ?? null, is_active: false })));
|
|
await mdl_Category.destroy({ where: { id: activeIds } });
|
|
|
|
logActivity(req.user?.user_id, 'bulk_archive_categories', { entityType: 'category', details: { ids: activeIds, count: activeIds.length } });
|
|
return R.success(res, `${activeIds.length} categor${activeIds.length !== 1 ? 'ies' : 'y'} archived.`, {
|
|
archived_ids: activeIds,
|
|
skipped_ids: ids.filter((id) => !activeIds.includes(id)),
|
|
});
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][BULK ARCHIVE]', err);
|
|
return R.error(res, 'Could not archive categories.', 500);
|
|
}
|
|
};
|
|
|
|
exports.getCategoryPermanentDeleteImpact = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id, { paranoid: false });
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
const course_count = await CourseProductCategory.count({ where: { category_id: req.params.id } });
|
|
return R.success(res, 'Category permanent-delete impact retrieved.', { course_count });
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][PERMANENT DELETE IMPACT]', err);
|
|
return R.error(res, 'Could not retrieve category permanent-delete impact.', 500);
|
|
}
|
|
};
|
|
|
|
exports.permanentlyDeleteCategory = async (req, res) => {
|
|
try {
|
|
const row = await mdl_Category.findByPk(req.params.id, { paranoid: false });
|
|
if (!row) return R.error(res, 'Category not found.', 404);
|
|
if (!row.deletedAt) return R.error(res, 'Category must be archived before it can be permanently deleted.', 400);
|
|
// course_product_categories.category_id has no DB-level cascade (only course_id does) —
|
|
// clean up the junction rows explicitly or they'd be left orphaned.
|
|
await CourseProductCategory.destroy({ where: { category_id: row.id } });
|
|
await row.destroy({ force: true });
|
|
logActivity(req.user?.user_id, 'permanently_delete_category', { entityType: 'category', entityId: row.id, details: { name: row.name } });
|
|
return R.success(res, 'Category permanently deleted.');
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][PERMANENT DELETE]', err);
|
|
return R.error(res, 'Could not permanently delete category.', 500);
|
|
}
|
|
};
|
|
|
|
exports.bulkRestoreCategories = async (req, res) => {
|
|
try {
|
|
const { ids } = req.body;
|
|
if (!Array.isArray(ids) || !ids.length) return R.error(res, 'No category IDs provided.', 400);
|
|
|
|
const rows = await mdl_Category.findAll({ where: { id: ids }, paranoid: false });
|
|
if (!rows.length) return R.error(res, 'No categories found.', 404);
|
|
|
|
const archivedRows = rows.filter((r) => r.deletedAt);
|
|
if (!archivedRows.length) return R.error(res, 'All selected categories are already active.', 400);
|
|
|
|
const archivedIds = archivedRows.map((r) => r.id);
|
|
await mdl_Category.restore({ where: { id: archivedIds } });
|
|
await mdl_Category.update({ deletedBy: null, is_active: true }, { where: { id: archivedIds }, paranoid: false });
|
|
|
|
logActivity(req.user?.user_id, 'bulk_restore_categories', { entityType: 'category', details: { ids: archivedIds, count: archivedIds.length } });
|
|
return R.success(res, `${archivedIds.length} categor${archivedIds.length !== 1 ? 'ies' : 'y'} restored.`, {
|
|
restored_ids: archivedIds,
|
|
skipped_ids: ids.filter((id) => !archivedIds.includes(id)),
|
|
});
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][BULK RESTORE]', err);
|
|
return R.error(res, 'Could not restore categories.', 500);
|
|
}
|
|
};
|
|
|
|
exports.bulkPermanentlyDeleteCategories = async (req, res) => {
|
|
try {
|
|
const { ids } = req.body;
|
|
if (!Array.isArray(ids) || !ids.length) return R.error(res, 'No category IDs provided.', 400);
|
|
|
|
const rows = await mdl_Category.findAll({ where: { id: ids }, paranoid: false });
|
|
if (!rows.length) return R.error(res, 'No categories found.', 404);
|
|
|
|
const archivedRows = rows.filter((r) => r.deletedAt);
|
|
if (!archivedRows.length) return R.error(res, 'All selected categories must be archived before they can be permanently deleted.', 400);
|
|
|
|
const archivedIds = archivedRows.map((r) => r.id);
|
|
await CourseProductCategory.destroy({ where: { category_id: archivedIds } });
|
|
await mdl_Category.destroy({ where: { id: archivedIds }, force: true });
|
|
|
|
logActivity(req.user?.user_id, 'bulk_permanently_delete_categories', { entityType: 'category', details: { ids: archivedIds, count: archivedIds.length } });
|
|
return R.success(res, `${archivedIds.length} categor${archivedIds.length !== 1 ? 'ies' : 'y'} permanently deleted.`, {
|
|
deleted_ids: archivedIds,
|
|
skipped_ids: ids.filter((id) => !archivedIds.includes(id)),
|
|
});
|
|
} catch (err) {
|
|
console.error('[ADMIN][CATEGORIES][BULK PERMANENT DELETE]', err);
|
|
return R.error(res, 'Could not permanently delete categories.', 500);
|
|
}
|
|
};
|