Support uploading structured JSON to create courses, classes, and students without wiping existing data, and merge user name fields while adding gender and registration details from source sheets.
109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
// /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 phoneNumber gender' })
|
|
.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 };
|