Last active
May 11, 2020 18:21
-
-
Save taxilian/58c058638b11ad4a11399acb899a866d to your computer and use it in GitHub Desktop.
Email address validation typescriptified model used by HamStudy.org, example of mongoose-decorators-ts use
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// import _ from 'lodash'; | |
// import moment from 'moment'; | |
// import express from 'express'; | |
// import uuid from 'uuid'; | |
import mongoose from 'mongoose'; | |
import {schemaDef, dateField, pre, ref, field, arrayField, required, ModelFromSchemaDef, IMongooseDocument, populateVirtual} from 'mongoose-decorators-ts'; | |
import emailVerify from 'email-verifier'; | |
import P from '../lib/PromiseTS'; | |
import _ from 'lodash'; | |
// Note that while requests aren't super expensive they are also not free | |
const whoisXmlApiKey = "at_7ytFXfrNboMG0G99Agvi7hGWfByte"; | |
const Verifier = new emailVerify(whoisXmlApiKey, { | |
checkCatchAll: true, | |
checkDisposable: true, | |
checkFree: true, | |
validateDNS: true, | |
validateSMTP: true, | |
}); | |
const WhoisXMLApiVerify = P.promisify(Verifier.verify, {context: Verifier}); | |
@schemaDef({ | |
indexes: [ | |
[{email: 1, confirmKey: 1}], | |
[{createdAt: 1}, {expireAfterSeconds: 60 * 60 * 24 * 14}], // expires after 14 days | |
], | |
plugins: [ | |
], | |
schema_options: { usePushEach: true, timestamps: true } | |
}) | |
class EmailAddrValidation { | |
/** This is the email address of the record */ | |
@field({ lowercase: true, trim: true }) | |
_id: string; | |
/** Automatically added by `timestamps: true` in schema_options */ | |
createdAt: Date; | |
/** Automatically added by `timestamps: true` in schema_options */ | |
updatedAt: Date; | |
////// These are fields cached from the WhoisXmlApi validation request | |
/** 'true' if there were syntax errors in the address */ | |
@required() formatCheck: "true" | "false"; | |
/** 'true' if the email address exists and can receive email over SMTP */ | |
@required() smtpCheck: "true" | "false"; | |
/** 'true' if the domain's DNS is configured correctly for email */ | |
@required() dnsCheck: "true" | "false"; | |
/** 'false' if the domain is not free, 'true' if it is */ | |
@required() freeCheck: "true" | "false"; | |
/** 'true' if the email address is disposable */ | |
@required() disposableCheck: "true" | "false"; | |
/** 'true' if the mail server has a catch-all address */ | |
@required() catchAllCheck: "true" | "false"; | |
/** If present, a list of all mail servers from MX records */ | |
@required() mxRecords?: string[]; | |
static async validateEmailAddr(this: typeof EmailAddrValidationModel, email: string) { | |
let validate = await this.findById(email.toLowerCase().trim()).exec(); | |
if (!validate) { | |
validate = new this({_id: email}); | |
try { | |
let data = await WhoisXMLApiVerify(email); | |
for (let f of <const>[ | |
"formatCheck", | |
"smtpCheck", | |
"dnsCheck", | |
"freeCheck", | |
"disposableCheck", | |
"catchAllCheck", | |
"mxRecords", | |
]) { | |
validate.set(f, data[f]); | |
} | |
await validate.save(); | |
} catch (err) { | |
return null; | |
} | |
} | |
return validate; | |
} | |
} | |
export const EmailAddrValidationModel = ModelFromSchemaDef<typeof EmailAddrValidation, EmailAddrValidation>(EmailAddrValidation); | |
export type EmailAddrValidationModel = IMongooseDocument<EmailAddrValidation>; | |
export default EmailAddrValidationModel; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// excerpt, not full file | |
@schemaDef({ | |
indexes: [ | |
[{ session_def: 1, sequence_num: 1, owner: 1 }, { unique: true }], | |
[{ start_date: 1, end_date: 1 }], | |
[{ owner: 1, date: 1, session_def: 1, sequence_num: 1 }], | |
[{ password: 1, date: 1 }, { sparse: true }], | |
[{ date: 1 }], | |
], | |
plugins: [ | |
{ plugin: createdModifiedPlugin, options: { index: true } }, | |
], | |
schema_options: { usePushEach: true, id: false } | |
}) | |
class SessionInstance { | |
_id: mongoose.Types.ObjectId; | |
// tslint:disable:variable-name | |
@ref('SessionDef', { required: true }) session_def: mongoose.Types.ObjectId; | |
@required sequence_num: number; | |
// Denormalized data duplicated from the sessiondef: | |
@ref('User', { required: true }) owner: mongoose.Types.ObjectId; | |
@dateField({ required: true }) date: Date; | |
@required({ enum: VECEnum }) vec: cData.VECList; | |
// -- | |
/** Overrides the owner's callsign if set */ | |
@field() | |
callsign: string; | |
/** Extra information needed for this VEC */ | |
@field() | |
vec_metadata: any; | |
/** Extra information requested by this VE */ | |
@field() | |
ve_metadata: any; | |
/** This field replaced the old ones and the old ones should not be used anymore */ | |
@arrayField(VELink) sessionVes: mongoose.Types.DocumentArray<VELink>; | |
/** @deprecated */ | |
@refArray('VE', mongoose.Types.ObjectId) veList: mongoose.Types.Array<mongoose.Types.ObjectId>; | |
/** @deprecated */ | |
@refArray('VE', mongoose.Types.ObjectId) signing_ves: mongoose.Types.Array<mongoose.Types.ObjectId>; | |
/** @deprecated */ | |
@field password: string; | |
@required({ default: 'pend', enum: Object.values(SessionStates) }) | |
state: SessionStates; | |
//////////// Populate Fields \\\\\\\\\\\\ | |
@populateVirtual({ref: 'SessionDef', localField: 'session_def', foreignField: '_id', justOne: true}) | |
readonly sessionDef?: SessionDefModel; | |
@populateVirtual({ | |
ref: 'Applicant', localField: '_id', foreignField: 'sInstance', | |
count: true}) | |
readonly applicantCount?: number; | |
@populateVirtual({ref: 'Applicant', localField: '_id', foreignField: 'sInstance', justOne: false}) | |
applicantObjects: ApplicantModel[]; | |
@populateVirtual({ | |
ref: 'VE', | |
localField: 'veList', foreignField: '_id', | |
justOne: false, options: <any>{select: "call vec display_name"} | |
}) | |
readonly veListDocs?: Pick<VEModel, 'call'|'vec'|'display_name'>[]; | |
@populateVirtual({ | |
ref: 'VE', | |
localField: 'signing_ves', foreignField: '_id', | |
justOne: false, options: <any>{select: "call vec display_name"} | |
}) | |
readonly signingVeDocs?: Pick<VEModel, 'call'|'vec'|'display_name'>[]; | |
@populateVirtual({ | |
ref: 'User', localField: 'owner', foreignField: '_id', | |
justOne: true, options: <any>{select: 'call email first last'}}) | |
readonly ownerDoc?: UserModel; | |
get vec_name() { | |
return cData.VECList[<'w5yi'>this.vec]; | |
} | |
// tslint:enable:variable-name | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment