'use strict'; const mongoose = require('mongoose'), Schema = mongoose.Schema, uniqid = require('uniqid'), mongoUtil = require('../helpers/mongo'), Setting = require('./setting'), RptVar = require('./rpt_var'), Country = require('./country'), { Errors } = require('../helpers/constants'), { AddressSchema } = require('./common'), { DEFAULT_LANG } = require('../helpers/constants'), { AppInputError } = require('../helpers/app_error'), { ensureSingleBillingAddress } = require('../helpers/user_helper'); const schema = new Schema({ /* The username is for uniquely identify a user and also used for sending mails to. For example: send user the password-reset email, sending reports, etc. */ username: { type: String, trim: true, index: { unique: true, partialFilterExpression: { username: { $type: 'string' } } }, set: v => (v === '' ? null : v) }, password: { type: String, trim: true, required: false, set: v => (v === '' ? null : v) }, name: { type: String, required: false }, // Legacy quick address. TODO: migrate to new address schema address: { type: String, required: false }, phone: { type: String, required: false }, email: { type: String, required: false, unique: false }, active: { type: Boolean, default: true }, /* The preffered language from the available ones within the FE (English(en) - default, Spanish(es), Portuguese(pt)) */ lang: { type: String, default: DEFAULT_LANG }, parent: { type: Schema.Types.ObjectId, ref: 'User', required: false }, // Reference to the Partner collection (optional) - used for customers with partner integrations partner: { type: Schema.Types.ObjectId, ref: 'User', required: false }, loggedInAt: { type: Date }, markedDelete: { type: Boolean, default: false }, // Users addresses. Only one of them can be marked as billing address. addresses: { type: [AddressSchema], default: [] }, // The migrated date for applicator and users paid before the migration to SM migratedDate: { type: Date, required: false, default: null }, needReview: { type: Boolean, required: false } }, { timestamps: true, discriminatorKey: 'kind', toJSON: { virtuals: true }, toObject: { virtuals: true }, strictQuery: false }); schema.virtual('Country', { ref: 'Country', localField: 'country', foreignField: 'code', justOne: true, }); class UserBaseSchema { static toUser(srcOjb) { if (!srcOjb) AppInputError.throw(); if (srcOjb['Country'] && typeof srcOjb['Country'] === 'object' && srcOjb['Country'].code) { srcOjb['country'] = srcOjb['Country'].code; } return srcOjb; } async markAsDeleted(session) { session && (session.startTransaction(mongoUtil.getTranOps())); try { // De-activate the username so same username can be re-used this.username && (this.username = this.username + '#' + uniqid()); this.active && (this.active = false); this.markedDelete = true; await this.save({ session }); } catch (error) { session && (session.abortTransaction()); throw error; } session && await mongoUtil.commitWithRetry(session); } async deleteMarked(session) { session && (session.startTransaction(mongoUtil.getTranOps())); try { await this.remove({ session }); } catch (error) { session && (session.abortTransaction()); throw error; } session && await mongoUtil.commitWithRetry(session); } } schema.loadClass(UserBaseSchema); async function clearRelatedData(doc) { if (!doc) return; await Setting.deleteMany({ userId: doc._id }).session(doc.$session()); await RptVar.deleteMany({ userId: doc._id }).session(doc.$session()); } async function updateFields(doc) { if (!doc) return; // Convert ref objects to its code/id for storage if (doc['Country'] && typeof doc['Country'] === 'object' && doc['Country'].code) { doc['country'] = doc['Country'].code; } // Ensure only one billing address exists using reusable utility function if (doc.addresses && Array.isArray(doc.addresses)) { doc.addresses = ensureSingleBillingAddress(doc.addresses); } } /** * Handle pre-update hooks middleware before updating one user. */ schema.pre(['updateOne', 'findOneAndUpdate'], async function () { await updateFields(this?._update); }); schema.pre(['save'], async function () { await updateFields(this); }); schema.post('remove', async function () { await clearRelatedData(this); }); schema.post('findOneAndRemove', async function (doc) { await clearRelatedData(doc); }); schema.post('findOneAndDelete', async function (doc) { await clearRelatedData(doc); }); module.exports = mongoose.model('User', schema);