added and fix some of things

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-03 12:03:02 +08:00
parent 6765c1faba
commit d246c09cdd
31 changed files with 724 additions and 510 deletions
+134 -4
View File
@@ -1,15 +1,32 @@
'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 rows = await mdl_Category.findAll({ order: [['name', 'ASC']], paranoid: false });
return R.success(res, 'Categories retrieved.', rows);
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);
@@ -33,7 +50,10 @@ exports.createCategory = async (req, res) => {
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 });
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) {
@@ -51,7 +71,10 @@ exports.updateCategory = async (req, res) => {
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 });
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) {
@@ -66,6 +89,7 @@ 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.');
@@ -79,7 +103,9 @@ 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) {
@@ -87,3 +113,107 @@ exports.restoreCategory = async (req, res) => {
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);
}
};