From 43404f2dfcbe97461acabcc222bb816058c2a55c Mon Sep 17 00:00:00 2001 From: Kavehhn174 Date: Wed, 26 Aug 2026 05:17:41 +0330 Subject: [PATCH] fix(professors, settings): add auto-migration for legacy professors, deep populate user in class/session/course services, and fix SMS bypass number persistence --- app.js | 3 + components/classes/classService.js | 66 +++++++-- components/courses/courseService.js | 14 +- components/dashboard/dashboardService.js | 5 +- components/professors/professorMigration.js | 139 ++++++++++++++++++ components/professors/professorModel.js | 40 +++-- components/professors/professorService.js | 52 ++++--- .../professors/professorUserMerge.test.js | 41 ++++++ components/sessions/sessionService.js | 10 +- components/settings/settingService.js | 20 ++- components/settings/smsBypass.test.js | 1 + seed.js | 3 + 12 files changed, 337 insertions(+), 57 deletions(-) create mode 100644 components/professors/professorMigration.js diff --git a/app.js b/app.js index 36e9dcc..eb41804 100644 --- a/app.js +++ b/app.js @@ -122,6 +122,9 @@ const PORT = config.PORT || 3000; const startServer = async () => { await connectDB(); + const { migrateAndLinkProfessorsToUsers } = require('./components/professors/professorMigration'); + await migrateAndLinkProfessorsToUsers(); + if (config.NODE_ENV === 'production') { logger.info('Running production bootstrap (roles + SuperAdmin from env)'); await seedDatabase({ disconnectOnComplete: false }); diff --git a/components/classes/classService.js b/components/classes/classService.js index 4e79db9..ce52f74 100644 --- a/components/classes/classService.js +++ b/components/classes/classService.js @@ -61,6 +61,7 @@ const normalizeNumberOfSessions = (value) => { }; const enrichClassForDisplay = (cls) => { + if (!cls) return cls; const tuitionFee = cls.tuitionFee || 0; const discount = cls.hasDiscount ? (cls.discount || 0) : 0; const finalTuitionFee = Math.max(0, tuitionFee - discount); @@ -68,7 +69,23 @@ const enrichClassForDisplay = (cls) => { if (cls.startDate) { daysUntilStart = Math.ceil((new Date(cls.startDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); } - return { ...cls, finalTuitionFee, daysUntilStart }; + + let professor = cls.professor; + if (professor && typeof professor === 'object') { + const profUser = professor.user && typeof professor.user === 'object' ? professor.user : null; + const profName = String(profUser?.name || professor.name || '').trim(); + const profPhone = String(profUser?.phoneNumber || professor.phoneNumber || '').trim(); + const parts = profName.split(/\s+/); + professor = { + ...professor, + name: parts[0] || profName || 'استاد', + surname: parts.length > 1 ? parts.slice(1).join(' ') : (professor.surname || ''), + fullName: profName || 'استاد', + phoneNumber: profPhone + }; + } + + return { ...cls, professor, finalTuitionFee, daysUntilStart }; }; const getAll = async (query = {}) => { @@ -83,8 +100,13 @@ const getAll = async (query = {}) => { filter.isDeleted = { $ne: true }; } - if (query.courseId) filter.course = query.courseId; - if (query.isActive !== undefined) filter.isActive = query.isActive === 'true'; + if (query.courseId) { + filter.course = query.courseId; + } + + if (query.isActive !== undefined) { + filter.isActive = query.isActive === 'true'; + } const searchTerm = getSearchTerm(query); if (searchTerm) { @@ -95,7 +117,10 @@ const getAll = async (query = {}) => { Class.find(filter) .select(CLASS_LIST_FIELDS) .populate({ path: 'course', select: 'title type price sectionCount hoursPerSection' }) - .populate({ path: 'professor', select: 'name surname' }) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber email nationalIdCode' } + }) .skip(skip).limit(limit).sort({ createdAt: -1 }).lean(), Class.countDocuments(filter) ]); @@ -106,8 +131,11 @@ const getAll = async (query = {}) => { const getOne = async (id) => { const cls = await Class.findById(id) .populate({ path: 'course', select: 'title type price sectionCount hoursPerSection' }) - .populate({ path: 'professor', select: 'name surname phoneNumber' }) - .populate({ path: 'students', select: 'name phoneNumber gender' }) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber email nationalIdCode' } + }) + .populate({ path: 'students', select: 'name phoneNumber gender' }) .lean(); if (!cls) throw new AppError('CLASS_NOT_FOUND'); return enrichClassForDisplay(cls); @@ -247,7 +275,10 @@ const getMyClasses = async (userId, query = {}) => { const [items, total] = await Promise.all([ Class.find(filter) .populate({ path: 'course', select: 'title type price description' }) - .populate({ path: 'professor', select: 'name surname' }) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber nationalIdCode' } + }) .skip(skip).limit(limit).sort({ startDate: -1, createdAt: -1 }).lean(), Class.countDocuments(filter) ]); @@ -271,7 +302,10 @@ const getPublicClasses = async (query = {}) => { Class.find(filter) .select('name course professor capacity tuitionFee hasDiscount discount freeSpots startDate endDate days startTime endTime') .populate({ path: 'course', select: 'title type description' }) - .populate({ path: 'professor', select: 'name surname' }) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber' } + }) .skip(skip).limit(limit).sort({ startDate: 1, createdAt: -1 }).lean(), Class.countDocuments(filter) ]); @@ -288,7 +322,10 @@ const getPublicOne = async (id) => { }) .select('name course professor capacity tuitionFee hasDiscount discount freeSpots startDate endDate days startTime endTime isActive') .populate({ path: 'course', select: 'title type description sectionCount hoursPerSection price' }) - .populate({ path: 'professor', select: 'name surname' }) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber' } + }) .lean(); if (!cls) throw new AppError('CLASS_NOT_FOUND', null, 'کلاس یافت نشد یا برای ثبت‌نام در دسترس نیست.'); @@ -298,7 +335,10 @@ const getPublicOne = async (id) => { const sendClassPlanToProfessor = async (classId) => { const classDoc = await Class.findById(classId) .populate('course', 'title') - .populate('professor', 'name surname phoneNumber'); + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name phoneNumber email' } + }); if (!classDoc) { throw new AppError('CLASS_NOT_FOUND', null, 'کلاس یافت نشد.'); @@ -309,11 +349,13 @@ const sendClassPlanToProfessor = async (classId) => { } const professor = classDoc.professor; - if (!professor.phoneNumber) { + const profUser = professor.user && typeof professor.user === 'object' ? professor.user : null; + const phoneNumber = profUser?.phoneNumber || professor.phoneNumber; + if (!phoneNumber) { throw new AppError('VALIDATION_FAILED', { field: 'phoneNumber' }, 'شماره همراه استاد ثبت نشده است.'); } - const profName = `${professor.name || ''} ${professor.surname || ''}`.trim() || 'استاد'; + const profName = profUser?.name || `${professor.name || ''} ${professor.surname || ''}`.trim() || 'استاد'; const className = classDoc.name || classDoc.course?.title || 'کلاس'; const classDays = formatClassDaysFromIndexes(classDoc.days) || 'طبق هماهنگی'; const classTimes = (classDoc.startTime && classDoc.endTime) diff --git a/components/courses/courseService.js b/components/courses/courseService.js index 8b3afae..9db5e5e 100644 --- a/components/courses/courseService.js +++ b/components/courses/courseService.js @@ -39,7 +39,10 @@ const getCourseById = async (id, { publicOnly = false } = {}) => { const filter = { _id: id }; if (publicOnly) filter.showOnFrontend = { $ne: false }; - const course = await Course.findOne(filter).populate('professor', 'name surname title expertise email phoneNumber'); + const course = await Course.findOne(filter).populate({ + path: 'professor', + populate: { path: 'user', select: 'name surname title expertise email phoneNumber' } + }); if (!course) { throw new AppError('COURSE_NOT_FOUND'); } @@ -69,7 +72,14 @@ const getAllCourses = async (queryParams, { publicOnly = false } = {}) => { } const [courses, totalCount] = await Promise.all([ - Course.find(filter).populate('professor', 'name surname').sort(finalSort).skip(skip).limit(limit), + Course.find(filter) + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name surname' } + }) + .sort(finalSort) + .skip(skip) + .limit(limit), Course.countDocuments(filter) ]); diff --git a/components/dashboard/dashboardService.js b/components/dashboard/dashboardService.js index a8a5a16..ba59f9c 100644 --- a/components/dashboard/dashboardService.js +++ b/components/dashboard/dashboardService.js @@ -30,7 +30,10 @@ const getAdminStats = async () => { .limit(8) .populate('course', 'title type') .populate('class', 'name') - .populate('professor', 'name surname') + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name surname' } + }) .lean() ]); diff --git a/components/professors/professorMigration.js b/components/professors/professorMigration.js new file mode 100644 index 0000000..3f87ff4 --- /dev/null +++ b/components/professors/professorMigration.js @@ -0,0 +1,139 @@ +// /components/professors/professorMigration.js +'use strict'; + +const mongoose = require('mongoose'); +const Professor = require('./professorModel'); +const User = require('../users/userModel'); +const Role = require('../roles/roleModel'); +const logger = require('../../utils/logger'); +const { allocatePlaceholderNationalId } = require('../../utils/nationalId'); +const { generateUsername, generateSimplePassword } = require('../../utils/credentials'); +const bcrypt = require('bcryptjs'); + +const ensureProfessorRole = async () => { + let role = await Role.findOne({ name: 'Professor' }); + if (!role) { + role = await Role.create({ + name: 'Professor', + description: 'استاد و مدرس دوره‌ها', + permissions: ['professors:read', 'classes:read', 'sessions:read', 'attendances:read'], + isSystem: true + }); + } + return role; +}; + +/** + * Automatically migrate and link all legacy Professor records to the Users table. + * Ensures every Professor document has a valid, populated `user` reference. + */ +const migrateAndLinkProfessorsToUsers = async () => { + try { + const professorRole = await ensureProfessorRole(); + const professors = await Professor.find({}).setOptions({ _skipUserPopulate: true }); + + if (!professors.length) { + logger.info('Professor migration: No professor records found.'); + return { total: 0, migrated: 0, alreadyLinked: 0 }; + } + + let migratedCount = 0; + let alreadyLinkedCount = 0; + + for (const prof of professors) { + const rawDoc = prof._doc || prof; + let linkedUser = null; + + // 1. Check if professor already has a valid user reference + if (prof.user && mongoose.Types.ObjectId.isValid(prof.user)) { + linkedUser = await User.findById(prof.user); + } + + if (linkedUser) { + // Ensure user role is Professor if not an admin + if (!linkedUser.role || (String(linkedUser.role) !== String(professorRole._id) && linkedUser.role?.name !== 'SuperAdmin' && linkedUser.role?.name !== 'Admin')) { + linkedUser.role = professorRole._id; + await linkedUser.save(); + } + alreadyLinkedCount++; + continue; + } + + // 2. Professor is not linked to a user. Find matching user by phone or national ID + const rawPhone = String(rawDoc.phoneNumber || rawDoc.phone || '').trim(); + const rawNationalId = String(rawDoc.nationalIdCode || rawDoc.nationalId || '').trim(); + const rawName = String(rawDoc.name || '').trim(); + const rawSurname = String(rawDoc.surname || '').trim(); + const fullName = [rawName, rawSurname].filter(Boolean).join(' ').trim() || 'استاد بدون نام'; + + if (rawPhone) { + linkedUser = await User.findOne({ phoneNumber: rawPhone }); + } + if (!linkedUser && rawNationalId) { + linkedUser = await User.findOne({ nationalIdCode: rawNationalId }); + } + + // 3. If matching user found, link and update role + if (linkedUser) { + if (!linkedUser.role || (String(linkedUser.role) !== String(professorRole._id) && linkedUser.role?.name !== 'SuperAdmin' && linkedUser.role?.name !== 'Admin')) { + linkedUser.role = professorRole._id; + } + if (!linkedUser.name && fullName) { + linkedUser.name = fullName; + } + if (!linkedUser.cardNumber && rawDoc.cardNumber) { + linkedUser.cardNumber = String(rawDoc.cardNumber).trim(); + } + if (!linkedUser.shabaNumber && (rawDoc.shabaNumber || rawDoc.iban)) { + linkedUser.shabaNumber = String(rawDoc.shabaNumber || rawDoc.iban).trim(); + } + await linkedUser.save(); + + prof.user = linkedUser._id; + await prof.save(); + migratedCount++; + logger.info(`Professor migration: Linked professor "${fullName}" (${prof._id}) to existing user (${linkedUser._id}).`); + continue; + } + + // 4. If no matching user found, create a new User for this professor + let nationalIdCode = rawNationalId; + if (!nationalIdCode) { + nationalIdCode = await allocatePlaceholderNationalId(rawPhone || fullName); + } + + const generatedUsername = await generateUsername(fullName); + const plainPassword = generateSimplePassword(); + const passwordHash = await bcrypt.hash(plainPassword, 10); + + const newUser = await User.create({ + name: fullName, + nationalIdCode, + phoneNumber: rawPhone || undefined, + email: rawDoc.email ? String(rawDoc.email).trim().toLowerCase() : undefined, + role: professorRole._id, + username: generatedUsername, + passwordHash, + cardNumber: rawDoc.cardNumber ? String(rawDoc.cardNumber).trim() : undefined, + shabaNumber: (rawDoc.shabaNumber || rawDoc.iban) ? String(rawDoc.shabaNumber || rawDoc.iban).trim() : undefined, + isActive: prof.isActive !== false + }); + + prof.user = newUser._id; + await prof.save(); + migratedCount++; + logger.info(`Professor migration: Created new user (${newUser._id}) for legacy professor "${fullName}" (${prof._id}).`); + } + + logger.info(`Professor migration complete: ${professors.length} total, ${migratedCount} newly linked/created, ${alreadyLinkedCount} already linked.`); + return { total: professors.length, migrated: migratedCount, alreadyLinked: alreadyLinkedCount }; + } catch (error) { + logger.error('Error during migrateAndLinkProfessorsToUsers:', error); + return { error: error.message }; + } +}; + +module.exports = { + migrateAndLinkProfessorsToUsers, + ensureProfessorRole +}; diff --git a/components/professors/professorModel.js b/components/professors/professorModel.js index 5e3f37b..23e28af 100644 --- a/components/professors/professorModel.js +++ b/components/professors/professorModel.js @@ -7,8 +7,6 @@ const professorSchema = new mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', - required: true, - unique: true, index: true }, bio: { @@ -29,16 +27,28 @@ const professorSchema = new mongoose.Schema({ } }, { timestamps: true, + strict: false, toJSON: { virtuals: true }, toObject: { virtuals: true } }); -// Virtual properties delegating personal details to the linked User model +// Auto-populate linked user document on find queries unless explicitly skipped +professorSchema.pre(/^find/, function (next) { + if (this.options?._skipUserPopulate !== true) { + this.populate({ + path: 'user', + select: 'name nationalIdCode phoneNumber email cardNumber shabaNumber role isActive' + }); + } + next(); +}); + +// Virtual properties delegating personal details to the linked User model with legacy fallback professorSchema.virtual('name').get(function () { if (this.user && typeof this.user === 'object' && this.user.name) { return this.user.name; } - return undefined; + return this._doc?.name || undefined; }); professorSchema.virtual('surname').get(function () { @@ -48,42 +58,42 @@ professorSchema.virtual('surname').get(function () { return parts.slice(1).join(' '); } } - return ''; + return this._doc?.surname || ''; }); professorSchema.virtual('nationalIdCode').get(function () { - if (this.user && typeof this.user === 'object') { + if (this.user && typeof this.user === 'object' && this.user.nationalIdCode) { return this.user.nationalIdCode; } - return undefined; + return this._doc?.nationalIdCode || undefined; }); professorSchema.virtual('phoneNumber').get(function () { - if (this.user && typeof this.user === 'object') { + if (this.user && typeof this.user === 'object' && this.user.phoneNumber) { return this.user.phoneNumber; } - return undefined; + return this._doc?.phoneNumber || undefined; }); professorSchema.virtual('email').get(function () { - if (this.user && typeof this.user === 'object') { + if (this.user && typeof this.user === 'object' && this.user.email) { return this.user.email; } - return undefined; + return this._doc?.email || undefined; }); professorSchema.virtual('cardNumber').get(function () { - if (this.user && typeof this.user === 'object') { + if (this.user && typeof this.user === 'object' && this.user.cardNumber) { return this.user.cardNumber; } - return undefined; + return this._doc?.cardNumber || undefined; }); professorSchema.virtual('shabaNumber').get(function () { - if (this.user && typeof this.user === 'object') { + if (this.user && typeof this.user === 'object' && this.user.shabaNumber) { return this.user.shabaNumber; } - return undefined; + return this._doc?.shabaNumber || undefined; }); module.exports = mongoose.model('Professor', professorSchema); diff --git a/components/professors/professorService.js b/components/professors/professorService.js index 43de6de..d24f279 100644 --- a/components/professors/professorService.js +++ b/components/professors/professorService.js @@ -41,30 +41,48 @@ const formatProfessorDoc = (doc) => { const raw = doc.toObject ? doc.toObject({ virtuals: true }) : doc; const user = raw.user && typeof raw.user === 'object' ? raw.user : null; - const rawName = String(user?.name || raw.name || '').trim(); - const parts = rawName.split(/\s+/); - let firstName = rawName; - let surname = ''; - if (parts.length > 1) { - firstName = parts[0]; - surname = parts.slice(1).join(' '); + const rawUserName = String(user?.name || '').trim(); + const rawDocName = String(raw.name || '').trim(); + const rawSurname = String(raw.surname || '').trim(); + + let fullName = rawUserName || (rawDocName ? `${rawDocName} ${rawSurname}`.trim() : ''); + let firstName = ''; + let surname = rawSurname; + + if (fullName) { + const parts = fullName.split(/\s+/); + if (parts.length > 1) { + firstName = parts[0]; + surname = parts.slice(1).join(' '); + } else { + firstName = fullName; + } + } else { + firstName = rawDocName; + fullName = `${firstName} ${surname}`.trim(); } + const rawPhone = String(user?.phoneNumber || raw.phoneNumber || raw.phone || '').trim(); + const rawNationalId = String(user?.nationalIdCode || raw.nationalIdCode || raw.nationalId || '').trim(); + const rawEmail = String(user?.email || raw.email || '').trim(); + const rawCard = String(user?.cardNumber || raw.cardNumber || '').trim(); + const rawShaba = String(user?.shabaNumber || raw.shabaNumber || raw.iban || '').trim(); + return { _id: raw._id, id: raw._id, user: user ? (user._id || user) : raw.user, userId: user ? (user._id || user) : raw.user, - name: firstName, - surname: surname || raw.surname || '', - fullName: rawName, - nationalIdCode: user?.nationalIdCode || raw.nationalIdCode || '', - nationalId: user?.nationalIdCode || raw.nationalIdCode || '', - phoneNumber: user?.phoneNumber || raw.phoneNumber || '', - phone: user?.phoneNumber || raw.phoneNumber || '', - email: user?.email || raw.email || '', - cardNumber: user?.cardNumber || raw.cardNumber || '', - shabaNumber: user?.shabaNumber || raw.shabaNumber || '', + name: firstName || fullName || 'استاد', + surname: surname || '', + fullName: fullName || firstName || 'استاد', + nationalIdCode: rawNationalId, + nationalId: rawNationalId, + phoneNumber: rawPhone, + phone: rawPhone, + email: rawEmail, + cardNumber: rawCard, + shabaNumber: rawShaba, bio: raw.bio || '', expertise: Array.isArray(raw.expertise) ? raw.expertise : [], courses: raw.courses || [], diff --git a/components/professors/professorUserMerge.test.js b/components/professors/professorUserMerge.test.js index eb0fb61..acc225d 100644 --- a/components/professors/professorUserMerge.test.js +++ b/components/professors/professorUserMerge.test.js @@ -57,4 +57,45 @@ test('Professor Model & User Merge Tests', async (t) => { assert.deepEqual(formatted.expertise, ['Web', 'Vue']); assert.equal(formatted.isActive, true); }); + + await t.test('formatProfessorDoc handles legacy documents without linked user', () => { + const profId = new mongoose.Types.ObjectId(); + const legacyDoc = { + _id: profId, + name: 'رضا', + surname: 'اکبری', + phoneNumber: '09351234567', + nationalIdCode: '1234567890', + email: 'reza@example.com', + cardNumber: '5022291012345678', + shabaNumber: 'IR980000000000000000000000', + bio: 'مدرس پایگاه داده', + isActive: true + }; + + const formatted = formatProfessorDoc(legacyDoc); + assert.equal(String(formatted._id), String(profId)); + assert.equal(formatted.name, 'رضا'); + assert.equal(formatted.surname, 'اکبری'); + assert.equal(formatted.fullName, 'رضا اکبری'); + assert.equal(formatted.phoneNumber, '09351234567'); + assert.equal(formatted.nationalIdCode, '1234567890'); + assert.equal(formatted.email, 'reza@example.com'); + }); + + await t.test('Professor virtuals fallback correctly on legacy doc', () => { + const prof = new Professor(); + prof._doc = { + name: 'مریم احمدی', + surname: 'احمدی', + phoneNumber: '09121112233', + nationalIdCode: '0076543210', + email: 'maryam@example.com' + }; + + assert.equal(prof.name, 'مریم احمدی'); + assert.equal(prof.surname, 'احمدی'); + assert.equal(prof.phoneNumber, '09121112233'); + assert.equal(prof.nationalIdCode, '0076543210'); + }); }); diff --git a/components/sessions/sessionService.js b/components/sessions/sessionService.js index 79560db..043f5ee 100644 --- a/components/sessions/sessionService.js +++ b/components/sessions/sessionService.js @@ -91,7 +91,10 @@ const getSessionById = async (id) => { select: 'name students capacity', populate: { path: 'students', select: 'name nationalIdCode phoneNumber gender' } }) - .populate('professor', 'name surname email phoneNumber') + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name surname email phoneNumber' } + }) .populate('attendanceList.user', 'name username nationalIdCode'); if (!session) { throw new AppError('SESSION_NOT_FOUND'); @@ -232,7 +235,10 @@ const populateSessionList = (query, { includeClassStudents = false } = {}) => { let chain = query .populate('course', 'title type') .populate('class', includeClassStudents ? 'name students' : 'name') - .populate('professor', 'name surname'); + .populate({ + path: 'professor', + populate: { path: 'user', select: 'name surname phoneNumber' } + }); return chain; }; diff --git a/components/settings/settingService.js b/components/settings/settingService.js index 36d7c4e..bf8b875 100644 --- a/components/settings/settingService.js +++ b/components/settings/settingService.js @@ -86,15 +86,16 @@ const toPublicTemplates = (storedMap, notificationMap = {}) => { const formatBypassNumbers = (list) => { if (!Array.isArray(list)) return []; return list.map((item) => { - const raw = item.toObject ? item.toObject() : item; + const raw = item && item.toObject ? item.toObject() : item; + if (!raw) return null; return { - _id: raw._id, - phoneNumber: raw.phoneNumber, + _id: raw._id ? String(raw._id) : undefined, + phoneNumber: raw.phoneNumber || '', label: raw.label || '', isActive: raw.isActive !== false, createdAt: raw.createdAt }; - }); + }).filter(Boolean); }; const getSettings = async () => { @@ -273,17 +274,20 @@ const saveSettings = async (body = {}) => { .map((item) => { const rawPhone = item.phoneNumber || item.phone; const normalized = normalizePhoneForBypass(rawPhone); + const validId = (item._id && mongoose.Types.ObjectId.isValid(String(item._id))) + ? new mongoose.Types.ObjectId(String(item._id)) + : new mongoose.Types.ObjectId(); return { - _id: item._id || undefined, - phoneNumber: normalized || rawPhone, + _id: validId, + phoneNumber: normalized || String(rawPhone || '').trim(), label: String(item.label || '').trim(), isActive: item.isActive !== false, - createdAt: item.createdAt || new Date() + createdAt: item.createdAt ? new Date(item.createdAt) : new Date() }; }) .filter((item) => item.phoneNumber && item.phoneNumber.length >= 10); - doc.set('smsBypassNumbers', nextBypass); + doc.smsBypassNumbers = nextBypass; doc.markModified('smsBypassNumbers'); } diff --git a/components/settings/smsBypass.test.js b/components/settings/smsBypass.test.js index 5768690..2118c41 100644 --- a/components/settings/smsBypass.test.js +++ b/components/settings/smsBypass.test.js @@ -2,6 +2,7 @@ const test = require('node:test'); const assert = require('node:assert/strict'); +const mongoose = require('mongoose'); const { normalizePhoneForBypass } = require('./messagingFlags'); test('SMS Bypass Numbers Utilities', async (t) => { diff --git a/seed.js b/seed.js index a3f61e3..575f69a 100644 --- a/seed.js +++ b/seed.js @@ -204,6 +204,9 @@ const seedDatabase = async ({ disconnectOnComplete = false } = {}) => { } } + const { migrateAndLinkProfessorsToUsers } = require('./components/professors/professorMigration'); + await migrateAndLinkProfessorsToUsers(); + console.log('Database seeding completed successfully.'); return { success: true,