assets and tier plans revamp

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-01 17:44:25 +08:00
parent 39c3c4566b
commit cae958b5d5
41 changed files with 1271 additions and 165 deletions
+82 -42
View File
@@ -5,54 +5,94 @@ const { Course, CourseProductCategory: mdl_CourseProductCategory } = require('..
const R = require('../../utils/response.util');
const logActivity = require('../../utils/logActivity.util');
// ─── PRODUCT (per course) ─────────────────────────────────────────────────────
// ─── PRODUCT (generic, keyed by purchasable_type + purchasable_id) ───────────
// Course/Unit/Lesson each get their own thin route + exported handler below,
// all delegating to these so the CRUD logic isn't tripled across the three
// content types — see routes/admin/products.routes.js.
exports.getCourseProduct = async (req, res) => {
try {
const product = await mdl_Product.findOne({ where: { course_id: req.params.courseId }, paranoid: false });
return R.success(res, 'Product retrieved.', product ?? null);
} catch (err) {
console.error('[ADMIN][PRODUCTS][GET]', err);
return R.error(res, 'Could not retrieve product.', 500);
async function getProductFor(purchasable_type, purchasable_id) {
return mdl_Product.findOne({ where: { purchasable_type, purchasable_id }, paranoid: false });
}
async function upsertProductFor(purchasable_type, purchasable_id, body, adminUserId) {
const { name, description, price, currency, access_days, is_active } = body;
if (!name || price == null) {
const err = new Error('name and price are required.');
err.status = 400;
throw err;
}
};
exports.upsertCourseProduct = async (req, res) => {
try {
const { courseId } = req.params;
const { name, description, price, currency, access_days, is_active } = req.body;
if (!name || price == null) return R.error(res, 'name and price are required.', 400);
const existing = await mdl_Product.findOne({ where: { purchasable_type, purchasable_id }, paranoid: false });
const existing = await mdl_Product.findOne({ where: { course_id: courseId }, paranoid: false });
if (existing) {
if (existing.deletedAt) await existing.restore();
await existing.update({ name, description: description ?? null, price, currency: currency ?? 'USD', access_days: access_days ?? null, is_active: is_active ?? true });
logActivity(req.user?.user_id, 'upsert_course_product', { entityType: 'product', details: { course_id: courseId, name } });
return R.success(res, 'Product updated.', existing);
}
const product = await mdl_Product.create({ course_id: courseId, name, description: description ?? null, price, currency: currency ?? 'USD', access_days: access_days ?? null, is_active: is_active ?? true });
logActivity(req.user?.user_id, 'upsert_course_product', { entityType: 'product', entityId: product.product_id, details: { course_id: courseId, name } });
return R.success(res, 'Product created.', product, 201);
} catch (err) {
console.error('[ADMIN][PRODUCTS][UPSERT]', err);
return R.error(res, 'Could not save product.', 500);
if (existing) {
if (existing.deletedAt) await existing.restore();
await existing.update({ name, description: description ?? null, price, currency: currency ?? 'USD', access_days: access_days ?? null, is_active: is_active ?? true });
logActivity(adminUserId, `upsert_${purchasable_type}_product`, { entityType: 'product', entityId: existing.id, details: { purchasable_type, purchasable_id, name } });
return { product: existing, created: false };
}
};
exports.removeCourseProduct = async (req, res) => {
try {
const product = await mdl_Product.findOne({ where: { course_id: req.params.courseId } });
if (!product) return R.error(res, 'Product not found.', 404);
await product.destroy();
logActivity(req.user?.user_id, 'remove_course_product', { entityType: 'product', details: { course_id: req.params.courseId } });
return R.success(res, 'Product removed.');
} catch (err) {
console.error('[ADMIN][PRODUCTS][REMOVE]', err);
return R.error(res, 'Could not remove product.', 500);
}
};
const product = await mdl_Product.create({ purchasable_type, purchasable_id, name, description: description ?? null, price, currency: currency ?? 'USD', access_days: access_days ?? null, is_active: is_active ?? true });
logActivity(adminUserId, `upsert_${purchasable_type}_product`, { entityType: 'product', entityId: product.id, details: { purchasable_type, purchasable_id, name } });
return { product, created: true };
}
async function removeProductFor(purchasable_type, purchasable_id, adminUserId) {
const product = await mdl_Product.findOne({ where: { purchasable_type, purchasable_id } });
if (!product) return false;
await product.destroy();
logActivity(adminUserId, `remove_${purchasable_type}_product`, { entityType: 'product', details: { purchasable_type, purchasable_id } });
return true;
}
function makeProductHandlers(purchasable_type, paramName) {
return {
get: async (req, res) => {
try {
const product = await getProductFor(purchasable_type, req.params[paramName]);
return R.success(res, 'Product retrieved.', product ?? null);
} catch (err) {
console.error(`[ADMIN][PRODUCTS][GET][${purchasable_type}]`, err);
return R.error(res, 'Could not retrieve product.', 500);
}
},
upsert: async (req, res) => {
try {
const { product, created } = await upsertProductFor(purchasable_type, req.params[paramName], req.body, req.user?.user_id);
return R.success(res, created ? 'Product created.' : 'Product updated.', product, created ? 201 : 200);
} catch (err) {
if (err.status === 400) return R.error(res, err.message, 400);
console.error(`[ADMIN][PRODUCTS][UPSERT][${purchasable_type}]`, err);
return R.error(res, 'Could not save product.', 500);
}
},
remove: async (req, res) => {
try {
const removed = await removeProductFor(purchasable_type, req.params[paramName], req.user?.user_id);
if (!removed) return R.error(res, 'Product not found.', 404);
return R.success(res, 'Product removed.');
} catch (err) {
console.error(`[ADMIN][PRODUCTS][REMOVE][${purchasable_type}]`, err);
return R.error(res, 'Could not remove product.', 500);
}
},
};
}
const courseProductHandlers = makeProductHandlers('course', 'courseId');
const unitProductHandlers = makeProductHandlers('unit', 'unitId');
const lessonProductHandlers = makeProductHandlers('lesson', 'lessonId');
exports.getCourseProduct = courseProductHandlers.get;
exports.upsertCourseProduct = courseProductHandlers.upsert;
exports.removeCourseProduct = courseProductHandlers.remove;
exports.getUnitProduct = unitProductHandlers.get;
exports.upsertUnitProduct = unitProductHandlers.upsert;
exports.removeUnitProduct = unitProductHandlers.remove;
exports.getLessonProduct = lessonProductHandlers.get;
exports.upsertLessonProduct = lessonProductHandlers.upsert;
exports.removeLessonProduct = lessonProductHandlers.remove;
// ─── CATEGORIES (per course) ──────────────────────────────────────────────────