'use strict'; const mdl_Product = require('../../models/courses/products.mdl'); const mdl_Category = require('../../models/courses/categories.mdl'); const { Course, CourseProductCategory: mdl_CourseProductCategory } = require('../../models/courses/courses.mdl'); const R = require('../../utils/response.util'); const logActivity = require('../../utils/logActivity.util'); // ─── 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. 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; } const existing = await mdl_Product.findOne({ where: { purchasable_type, purchasable_id }, 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(adminUserId, `upsert_${purchasable_type}_product`, { entityType: 'product', entityId: existing.id, details: { purchasable_type, purchasable_id, name } }); return { product: existing, created: false }; } 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'); exports.getCourseProduct = courseProductHandlers.get; exports.upsertCourseProduct = courseProductHandlers.upsert; exports.removeCourseProduct = courseProductHandlers.remove; // ─── CATEGORIES (per course) ────────────────────────────────────────────────── exports.getCourseCategories = async (req, res) => { try { const course = await Course.findByPk(req.params.courseId, { include: [{ model: mdl_Category, as: 'categories', through: { attributes: [] } }], }); if (!course) return R.error(res, 'Course not found.', 404); return R.success(res, 'Course categories retrieved.', course.categories ?? []); } catch (err) { console.error('[ADMIN][PRODUCTS][GET CATEGORIES]', err); return R.error(res, 'Could not retrieve course categories.', 500); } }; exports.syncCourseCategories = async (req, res) => { try { const { courseId } = req.params; const { category_ids = [] } = req.body; await mdl_CourseProductCategory.destroy({ where: { course_id: courseId } }); if (category_ids.length > 0) { await mdl_CourseProductCategory.bulkCreate( category_ids.map((id) => ({ course_id: courseId, category_id: id })) ); } logActivity(req.user?.user_id, 'sync_course_categories', { entityType: 'product', details: { course_id: courseId, category_ids, count: category_ids.length } }); return R.success(res, 'Course categories updated.'); } catch (err) { console.error('[ADMIN][PRODUCTS][SYNC CATEGORIES]', err); return R.error(res, 'Could not sync course categories.', 500); } };