diff --git a/package-lock.json b/package-lock.json index ce33163399..d5ea0aacb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17433,17 +17433,17 @@ "integrity": "sha1-D3ca0W9IOuZfQoeWlCjp+8SqYYE=" }, "mongoose": { - "version": "5.11.10", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.11.10.tgz", - "integrity": "sha512-daE2L6VW7WNywv7tL2KUkBViWvODbzr50Of1kJpIbzW3w3N5/TYcgSmhCsEDWfYGQXbun2rdd7+sOdsEC8zQSQ==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.3.tgz", + "integrity": "sha512-frsSR9yeldaRpSUeTegXCSB0Tu5UGq8sHuHBuEV31Jk3COyxlKFQPL7UsdMhxPUCmk74FpOYSmNwxhWBEqgzQg==", "requires": { "@types/mongodb": "^3.5.27", "bson": "^1.1.4", "kareem": "2.3.2", - "mongodb": "3.6.3", + "mongodb": "3.6.5", "mongoose-legacy-pluralize": "1.0.2", "mpath": "0.8.3", - "mquery": "3.2.3", + "mquery": "3.2.5", "ms": "2.1.2", "regexp-clone": "1.0.0", "safe-buffer": "5.2.1", @@ -17452,14 +17452,14 @@ }, "dependencies": { "bson": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", - "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==" }, "mongodb": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", - "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz", + "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==", "requires": { "bl": "^2.2.1", "bson": "^1.1.4", @@ -17543,9 +17543,9 @@ "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA==" }, "mquery": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.3.tgz", - "integrity": "sha512-cIfbP4TyMYX+SkaQ2MntD+F2XbqaBHUYWk3j+kqdDztPWok3tgyssOZxMHMtzbV1w9DaSlvEea0Iocuro41A4g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.5.tgz", + "integrity": "sha512-VjOKHHgU84wij7IUoZzFRU07IAxd5kWJaDmyUzQlbjHjyoeK5TNeeo8ZsFDtTYnSgpW6n/nMNIHvE3u8Lbrf4A==", "requires": { "bluebird": "3.5.1", "debug": "3.1.0", diff --git a/package.json b/package.json index 73be391b0f..2b861f686d 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "lodash": "^4.17.21", "moment-timezone": "0.5.33", "mongodb-uri": "^0.9.7", - "mongoose": "^5.11.10", + "mongoose": "^5.12.3", "multiparty": ">=4.2.2", "neverthrow": "^4.2.1", "ng-infinite-scroll": "^1.3.0", diff --git a/src/app/models/admin_verification.server.model.ts b/src/app/models/admin_verification.server.model.ts index cbce56da2c..3f5d675999 100644 --- a/src/app/models/admin_verification.server.model.ts +++ b/src/app/models/admin_verification.server.model.ts @@ -12,7 +12,10 @@ import { USER_SCHEMA_ID } from './user.server.model' export const ADMIN_VERIFICATION_SCHEMA_ID = 'AdminVerification' -const AdminVerificationSchema = new Schema( +const AdminVerificationSchema = new Schema< + IAdminVerificationSchema, + IAdminVerificationModel +>( { admin: { type: Schema.Types.ObjectId, diff --git a/src/app/models/form.server.model.ts b/src/app/models/form.server.model.ts index aa8b879aed..460cb5765b 100644 --- a/src/app/models/form.server.model.ts +++ b/src/app/models/form.server.model.ts @@ -131,7 +131,7 @@ const EncryptedFormSchema = new Schema({ }, }) -const EmailFormSchema = new Schema({ +const EmailFormSchema = new Schema({ emails: { type: [ { @@ -159,7 +159,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { const User = getUserModel(db) // Schema - const FormSchema = new Schema( + const FormSchema = new Schema( { title: { type: String, @@ -342,7 +342,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Add discriminators for the various field types. const FormFieldPath = FormSchema.path( 'form_fields', - ) as Schema.Types.DocumentArray + ) as Schema.Types.DocumentArrayWithLooseDiscriminator const TableFieldSchema = createTableFieldSchema() @@ -376,7 +376,7 @@ const compileFormModel = (db: Mongoose): IFormModel => { FormFieldPath.discriminator(BasicField.Table, TableFieldSchema) const TableColumnPath = TableFieldSchema.path( 'columns', - ) as Schema.Types.DocumentArray + ) as Schema.Types.DocumentArrayWithLooseDiscriminator TableColumnPath.discriminator( BasicField.ShortText, createShortTextFieldSchema(), @@ -389,13 +389,13 @@ const compileFormModel = (db: Mongoose): IFormModel => { // Discriminator defines all possible values of startPage.logo const StartPageLogoPath = FormSchema.path( 'startPage.logo', - ) as Schema.Types.DocumentArray + ) as Schema.Types.DocumentArrayWithLooseDiscriminator StartPageLogoPath.discriminator(FormLogoState.Custom, CustomFormLogoSchema) // Discriminator defines different logic types const FormLogicPath = FormSchema.path( 'form_logics', - ) as Schema.Types.DocumentArray + ) as Schema.Types.DocumentArrayWithLooseDiscriminator FormLogicPath.discriminator(LogicType.ShowFields, ShowFieldsLogicSchema) FormLogicPath.discriminator(LogicType.PreventSubmit, PreventSubmitLogicSchema) @@ -458,12 +458,6 @@ const compileFormModel = (db: Mongoose): IFormModel => { } } - FormSchema.methods.getSettings = function ( - this: IFormDocument, - ): FormSettings { - return pick(this, FORM_SETTING_FIELDS) - } - // Archives form. FormSchema.methods.archive = function (this: IFormSchema) { // Return instantly when form is already archived. @@ -475,8 +469,16 @@ const compileFormModel = (db: Mongoose): IFormModel => { return this.save() } + const FormDocumentSchema = (FormSchema as unknown) as Schema + + FormDocumentSchema.methods.getSettings = function ( + this: IFormDocument, + ): FormSettings { + return pick(this, FORM_SETTING_FIELDS) + } + // Transfer ownership of the form to another user - FormSchema.methods.transferOwner = async function ( + FormDocumentSchema.methods.transferOwner = async function ( this: IFormDocument, currentOwner: IUserSchema, newOwner: IUserSchema, diff --git a/src/app/models/form_statistics_total.server.model.ts b/src/app/models/form_statistics_total.server.model.ts index 573c61effb..212eaec6b7 100644 --- a/src/app/models/form_statistics_total.server.model.ts +++ b/src/app/models/form_statistics_total.server.model.ts @@ -12,7 +12,10 @@ const FORM_STATS_TOTAL_SCHEMA_ID = 'FormStatisticsTotal' const FORM_STATS_COLLECTION_NAME = 'formStatisticsTotal' const compileFormStatisticsTotalModel = (db: Mongoose) => { - const FormStatisticsTotalSchema = new Schema( + const FormStatisticsTotalSchema = new Schema< + IFormStatisticsTotalSchema, + IFormStatisticsTotalModel + >( { formId: { type: Schema.Types.ObjectId, diff --git a/src/app/models/login.server.model.ts b/src/app/models/login.server.model.ts index c0a2b1f8b5..b39a4b812b 100644 --- a/src/app/models/login.server.model.ts +++ b/src/app/models/login.server.model.ts @@ -14,7 +14,7 @@ import { USER_SCHEMA_ID } from './user.server.model' export const LOGIN_SCHEMA_ID = 'Login' -const LoginSchema = new Schema( +const LoginSchema = new Schema( { admin: { type: Schema.Types.ObjectId, @@ -133,7 +133,7 @@ LoginSchema.statics.aggregateLoginStats = function ( } const compileLoginModel = (db: Mongoose) => - db.model(LOGIN_SCHEMA_ID, LoginSchema) as ILoginModel + db.model(LOGIN_SCHEMA_ID, LoginSchema) /** * Retrieves the Login model on the given Mongoose instance. If the model is @@ -143,7 +143,7 @@ const compileLoginModel = (db: Mongoose) => */ const getLoginModel = (db: Mongoose): ILoginModel => { try { - return db.model(LOGIN_SCHEMA_ID) as ILoginModel + return db.model(LOGIN_SCHEMA_ID) } catch { return compileLoginModel(db) } diff --git a/src/app/models/submission.server.model.ts b/src/app/models/submission.server.model.ts index f27c02e9fb..0bf1b1b2d9 100644 --- a/src/app/models/submission.server.model.ts +++ b/src/app/models/submission.server.model.ts @@ -25,7 +25,7 @@ import { FORM_SCHEMA_ID } from './form.server.model' export const SUBMISSION_SCHEMA_ID = 'Submission' -const SubmissionSchema = new Schema( +const SubmissionSchema = new Schema( { form: { type: Schema.Types.ObjectId, @@ -143,7 +143,10 @@ const webhookResponseSchema = new Schema( }, ) -const EncryptSubmissionSchema = new Schema({ +const EncryptSubmissionSchema = new Schema< + IEncryptedSubmissionSchema, + IEncryptSubmissionModel +>({ encryptedContent: { type: String, trim: true, @@ -347,15 +350,15 @@ const compileSubmissionModel = (db: Mongoose): ISubmissionModel => { const Submission = db.model('Submission', SubmissionSchema) Submission.discriminator(SubmissionType.Email, EmailSubmissionSchema) Submission.discriminator(SubmissionType.Encrypt, EncryptSubmissionSchema) - return db.model( + return db.model( SUBMISSION_SCHEMA_ID, SubmissionSchema, - ) as ISubmissionModel + ) } const getSubmissionModel = (db: Mongoose): ISubmissionModel => { try { - return db.model(SUBMISSION_SCHEMA_ID) as ISubmissionModel + return db.model(SUBMISSION_SCHEMA_ID) } catch { return compileSubmissionModel(db) } diff --git a/src/app/models/token.server.model.ts b/src/app/models/token.server.model.ts index f4d2538956..c07699a2b5 100644 --- a/src/app/models/token.server.model.ts +++ b/src/app/models/token.server.model.ts @@ -4,7 +4,7 @@ import { IToken, ITokenModel, ITokenSchema } from '../../types' export const TOKEN_SCHEMA_ID = 'Token' -const TokenSchema = new Schema({ +const TokenSchema = new Schema({ email: { type: String, required: true, diff --git a/src/app/models/user.server.model.ts b/src/app/models/user.server.model.ts index f0fbfc20eb..84c334b4e7 100644 --- a/src/app/models/user.server.model.ts +++ b/src/app/models/user.server.model.ts @@ -18,7 +18,7 @@ export const USER_SCHEMA_ID = 'User' const compileUserModel = (db: Mongoose) => { const Agency = getAgencyModel(db) - const UserSchema: Schema = new Schema( + const UserSchema: Schema = new Schema( { email: { type: String, @@ -59,7 +59,8 @@ const compileUserModel = (db: Mongoose) => { if (!phoneNumber) return false return phoneNumber.isValid() }, - message: (props) => `${props.value} is not a valid mobile number`, + message: (props: { value: string }) => + `${props.value} is not a valid mobile number`, }, }, lastAccessed: Date, diff --git a/src/app/modules/bounce/bounce.model.ts b/src/app/modules/bounce/bounce.model.ts index 8f1d9934d0..de7d7f4d47 100644 --- a/src/app/modules/bounce/bounce.model.ts +++ b/src/app/modules/bounce/bounce.model.ts @@ -23,7 +23,7 @@ export interface IBounceModel extends Model { ) => IBounceSchema } -const BounceSchema = new Schema({ +const BounceSchema = new Schema({ formId: { type: Schema.Types.ObjectId, ref: FORM_SCHEMA_ID, diff --git a/src/app/modules/myinfo/myinfo_hash.model.ts b/src/app/modules/myinfo/myinfo_hash.model.ts index bf53a7e406..03f95905f9 100644 --- a/src/app/modules/myinfo/myinfo_hash.model.ts +++ b/src/app/modules/myinfo/myinfo_hash.model.ts @@ -7,7 +7,7 @@ import { FORM_SCHEMA_ID } from '../../models/form.server.model' export const MYINFO_HASH_SCHEMA_ID = 'MyInfoHash' -const MyInfoHashSchema = new Schema( +const MyInfoHashSchema = new Schema( { // We stored a hashed uinFin using a salt stored as a env var // Note: key name not updated to reflect this for backward compatibility purposes diff --git a/src/app/modules/submission/submission.service.ts b/src/app/modules/submission/submission.service.ts index 45f7ce5beb..50619491c7 100644 --- a/src/app/modules/submission/submission.service.ts +++ b/src/app/modules/submission/submission.service.ts @@ -227,7 +227,7 @@ export const getFormSubmissionsCount = ( * @returns ok(true) if all emails were sent successfully * @returns err(SendEmailConfirmationError) if any email failed to be sent */ -export const sendEmailConfirmations = ({ +export const sendEmailConfirmations = ({ form, submission, parsedResponses, @@ -235,7 +235,7 @@ export const sendEmailConfirmations = ({ attachments, }: { form: IPopulatedForm - submission: ISubmissionSchema + submission: S parsedResponses: ProcessedFieldResponse[] autoReplyData?: EmailRespondentConfirmationField[] attachments?: IAttachmentInfo[] diff --git a/src/app/modules/verification/verification.model.ts b/src/app/modules/verification/verification.model.ts index e1be83a247..53d96c3d82 100644 --- a/src/app/modules/verification/verification.model.ts +++ b/src/app/modules/verification/verification.model.ts @@ -33,7 +33,10 @@ const VerificationFieldSchema = new Schema({ }) const compileVerificationModel = (db: Mongoose): IVerificationModel => { - const VerificationSchema = new Schema({ + const VerificationSchema = new Schema< + IVerificationSchema, + IVerificationModel + >({ formId: { type: Schema.Types.ObjectId, ref: FORM_SCHEMA_ID, diff --git a/src/app/services/sms/sms_count.server.model.ts b/src/app/services/sms/sms_count.server.model.ts index ea27658a2f..e7638e2ac8 100644 --- a/src/app/services/sms/sms_count.server.model.ts +++ b/src/app/services/sms/sms_count.server.model.ts @@ -83,7 +83,7 @@ const BouncedSubmissionSmsCountSchema = new Schema { - const SmsCountSchema = new Schema( + const SmsCountSchema = new Schema( { msgSrvcSid: { type: String, diff --git a/src/types/vendor/mongoose.d.ts b/src/types/vendor/mongoose.d.ts new file mode 100644 index 0000000000..917356050f --- /dev/null +++ b/src/types/vendor/mongoose.d.ts @@ -0,0 +1,33 @@ +/** + * Additional type declarations for mongoose to fit our use case, + * to accommodate the non-standard but compatible use of types + * in schema registration + */ +declare module 'mongoose' { + namespace Schema { + namespace Types { + /** + * A DocumentArray with a discriminator function that takes in a + * type-generic Schema. + */ + class DocumentArrayWithLooseDiscriminator extends DocumentArray { + /** + * In the built-in type declarations in + * version 5.12 of mongoose, discriminator() expects a Schema + * (and hence Schema). This does not work; a Schema + * for a subtype of Document is not a Schema for a Document, as + * Schema.methods expect to operate on the Schema's document type, + * which is not necessarily a Document. + * + * Address this by overriding the definition and provide a type-generic + * Schema argument. + */ + discriminator( + name: string, + schema: Schema, + tag?: string, + ): unknown + } + } + } +}