• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opengovsg / FormSG / 12667305727

08 Jan 2025 09:22AM UTC coverage: 72.402% (+0.005%) from 72.397%
12667305727

push

github

GitHub
fix(deps): bump cookie, cookie-parser, express-session and maildev

2725 of 4594 branches covered (59.32%)

Branch coverage included in aggregate %.

9962 of 12929 relevant lines covered (77.05%)

43.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/src/app/models/user.server.model.ts
1
import { parsePhoneNumberFromString } from 'libphonenumber-js/mobile'
148✔
2
import { CallbackError, Mongoose, Schema } from 'mongoose'
148✔
3
// https://stackoverflow.com/a/61679809
4
import { MongoError } from 'mongoose/node_modules/mongodb'
5
import validator from 'validator'
148✔
6

7
import {
8
  AgencyDocument,
9
  IUser,
10
  IUserModel,
11
  IUserSchema,
12
  PublicUser,
13
} from '../../types'
14

15
import getAgencyModel, { AGENCY_SCHEMA_ID } from './agency.server.model'
148✔
16

17
export const USER_SCHEMA_ID = 'User'
148✔
18

19
const compileUserModel = (db: Mongoose) => {
148✔
20
  const Agency = getAgencyModel(db)
95✔
21

22
  const UserSchema: Schema<IUserSchema, IUserModel> = new Schema(
95✔
23
    {
24
      email: {
25
        type: String,
26
        // Ensure lowercase email addresses are stored in the database.
27
        set: (v: string) => v.toLowerCase(),
1,492✔
28
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
29
        // @ts-ignore
30
        trim: true,
31
        unique: true,
32
        required: [true, 'Please enter your email'],
33
        validate: {
34
          // Check if email entered exists in the Agency collection
35
          validator: async (value: string) => {
1,148✔
36
            if (!validator.isEmail(value)) {
1,148✔
37
              return false
1✔
38
            }
39

40
            const emailDomain = value.split('@').pop()
1,147✔
41
            try {
1,147✔
42
              const agency = await Agency.findOne({ emailDomain })
1,147✔
43
              return !!agency
1,147✔
44
            } catch {
45
              return false
×
46
            }
47
          },
48
          message: 'This email is not a valid agency email',
49
        },
50
      },
51
      agency: {
52
        type: Schema.Types.ObjectId,
53
        ref: AGENCY_SCHEMA_ID,
54
        required: 'Agency is required',
55
      },
56
      contact: {
57
        type: String,
58
        validate: {
59
          // Check if phone number is valid.
60
          validator: function (value: string) {
61
            const phoneNumber = parsePhoneNumberFromString(value)
13✔
62
            if (!phoneNumber) return false
13✔
63
            return phoneNumber.isValid()
12✔
64
          },
65
          message: (props: { value: string }) =>
66
            `${props.value} is not a valid mobile number`,
1✔
67
        },
68
      },
69
      lastAccessed: Date,
70
      updatedAt: {
71
        type: Date,
72
        default: () => Date.now(),
1,149✔
73
      },
74
      betaFlags: {
75
        payment: Boolean,
76
        children: Boolean,
77
        postmanSms: Boolean,
78
        mrfEmailNotifications: Boolean, // Previously used for MRF email notifications, not currently used
79
        mrfAdminSubmissionKey: Boolean,
80
        mrfConditionalRouting: Boolean,
81
        mfb: Boolean,
82
        multiLangTranslation: Boolean,
83
      },
84
      flags: {
85
        type: Schema.Types.Map, // of SeenFlags
86
        of: Number,
87
      },
88
      apiToken: {
89
        select: false,
90
        type: {
91
          keyHash: {
92
            type: String,
93
          },
94
          createdAt: Date,
95
          lastUsedAt: Date,
96
          isPlatform: {
97
            type: Boolean,
98
          },
99
        },
100
      },
101
    },
102
    {
103
      timestamps: {
104
        createdAt: 'created',
105
        updatedAt: false,
106
      },
107
    },
108
  )
109

110
  // Hooks
111
  /**
112
   * Unique key violation custom error middleware.
113
   *
114
   * Used because the `unique` schema option is not a validator, and will not
115
   * throw a ValidationError. Instead, another error will be thrown, which will
116
   * have to be caught here to output the expected error message.
117
   *
118
   * See: https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key.
119
   */
120
  UserSchema.post<IUserSchema>(
95✔
121
    'save',
122
    function (err: Error, _doc: unknown, next: (err?: CallbackError) => void) {
123
      if (err) {
6!
124
        if (
6✔
125
          ['MongoError', 'MongoServerError'].includes(err.name) &&
7✔
126
          (err as MongoError)?.code === 11000
3!
127
        ) {
128
          next(new Error('Account already exists with this email'))
1✔
129
        } else {
130
          next(err)
5✔
131
        }
132
      } else {
133
        next()
×
134
      }
135
    },
136
  )
137

138
  // Methods
139
  UserSchema.methods.getPublicView = function (): PublicUser {
95✔
140
    // Return public view of nested agency document if populated.
141
    return {
18✔
142
      agency: this.populated('agency')
18✔
143
        ? (this.agency as AgencyDocument).getPublicView()
144
        : this.agency,
145
    }
146
  }
147

148
  // Statics
149
  /**
150
   * Upserts given user details into User collection.
151
   */
152
  UserSchema.statics.upsertUser = async function (
95✔
153
    upsertParams: Pick<IUser, 'email' | 'agency' | 'lastAccessed'>,
154
  ) {
155
    return this.findOneAndUpdate(
333✔
156
      { email: upsertParams.email },
157
      { $set: upsertParams },
158
      {
159
        upsert: true,
160
        new: true,
161
        runValidators: true,
162
        setDefaultsOnInsert: true,
163
      },
164
    ).populate({
165
      path: 'agency',
166
      model: AGENCY_SCHEMA_ID,
167
    })
168
  }
169

170
  /**
171
   * Finds the contact numbers for all given email addresses which exist in the
172
   * User collection.
173
   */
174
  UserSchema.statics.findContactNumbersByEmails = async function (
95✔
175
    emails: string[],
176
  ) {
177
    return this.find()
4✔
178
      .where('email')
179
      .in(emails)
180
      .select('-_id')
181
      .select('email contact')
182
      .lean()
183
      .exec()
184
  }
185

186
  return db.model<IUserSchema, IUserModel>(USER_SCHEMA_ID, UserSchema)
95✔
187
}
188

189
/**
190
 * Retrieves the User model on the given Mongoose instance. If the model is
191
 * not registered yet, the model will be registered and returned.
192
 * @param db The mongoose instance to retrieve the User model from
193
 * @returns The User model
194
 */
195
const getUserModel = (db: Mongoose): IUserModel => {
148✔
196
  try {
931✔
197
    return db.model(USER_SCHEMA_ID) as IUserModel
931✔
198
  } catch {
199
    return compileUserModel(db)
95✔
200
  }
201
}
202

203
export default getUserModel
148✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc