// /components/classes/classService.js 'use strict'; const Class = require('./classModel'); const User = require('../users/userModel'); const AppError = require('../../utils/AppError'); const { calculateMeta } = require('../../utils/pagination'); const { sendClassRegisteredSms } = require('../../utils/senders/smsMessages'); const logger = require('../../utils/logger'); const getAll = async (query) => { const page = parseInt(query.page) || 1; const limit = Math.min(parseInt(query.limit) || 20, 200); const skip = (page - 1) * limit; const filter = {}; if (query.courseId) filter.course = query.courseId; if (query.isActive !== undefined) filter.isActive = query.isActive === 'true'; const [items, total] = await Promise.all([ Class.find(filter) .populate({ path: 'course', select: 'title type price' }) .populate({ path: 'professor', select: 'name surname' }) .skip(skip).limit(limit).sort({ createdAt: -1 }).lean(), Class.countDocuments(filter) ]); return { data: items, meta: calculateMeta(total, page, limit) }; }; const getOne = async (id) => { const cls = await Class.findById(id) .populate({ path: 'course', select: 'title type price' }) .populate({ path: 'professor', select: 'name surname phoneNumber' }) .populate({ path: 'students', select: 'name surname phoneNumber' }) .lean(); if (!cls) throw new AppError('CLASS_NOT_FOUND'); return cls; }; const create = async (body) => { const cls = await Class.create(body); return getOne(cls._id); }; const update = async (id, body) => { const cls = await Class.findByIdAndUpdate(id, body, { new: true, runValidators: true }) .populate({ path: 'course', select: 'title' }) .lean(); if (!cls) throw new AppError('CLASS_NOT_FOUND'); return cls; }; const remove = async (id) => { const cls = await Class.findByIdAndDelete(id); if (!cls) throw new AppError('CLASS_NOT_FOUND'); }; const registerUsers = async (classId, userIds) => { const cls = await Class.findById(classId).populate({ path: 'course', select: 'title' }); if (!cls) throw new AppError('CLASS_NOT_FOUND'); const toAdd = (userIds || []).filter( (id) => !cls.students.map((s) => s.toString()).includes(id.toString()) ); if (toAdd.length === 0) { return getOne(classId); } cls.students.push(...toAdd); await cls.save(); const classLabel = cls.name || cls.course?.title || 'کلاس'; const users = await User.find({ _id: { $in: toAdd } }).select('phoneNumber').lean(); await Promise.all( users.map(async (user) => { if (!user.phoneNumber) return; try { await sendClassRegisteredSms(user.phoneNumber, classLabel, user._id); } catch (err) { logger.error(`[registerUsers] SMS failed for ${user.phoneNumber}: ${err.message}`); } }) ); return getOne(classId); }; const getMyClasses = async (userId, query = {}) => { const page = parseInt(query.page) || 1; const limit = Math.min(parseInt(query.limit) || 20, 200); const skip = (page - 1) * limit; const filter = { students: userId }; if (query.isActive !== undefined) filter.isActive = query.isActive === 'true'; const [items, total] = await Promise.all([ Class.find(filter) .populate({ path: 'course', select: 'title type price description' }) .populate({ path: 'professor', select: 'name surname' }) .skip(skip).limit(limit).sort({ startDate: -1, createdAt: -1 }).lean(), Class.countDocuments(filter) ]); return { data: items, meta: calculateMeta(total, page, limit) }; }; module.exports = { getAll, getOne, create, update, remove, registerUsers, getMyClasses };