mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
122 lines
5.7 KiB
JavaScript
122 lines
5.7 KiB
JavaScript
'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);
|
|
}
|
|
};
|