Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: migrate preview admin form endpoint to Typescript #864

Merged
merged 20 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0f92625
feat(AdminFormCtl): add handlePreviewAdminForm handler fn
karrui Dec 14, 2020
20597d3
test(AdminFormCtl): add tests for handlePreviewAdminForm
karrui Dec 14, 2020
85aadc9
ref(AdminFormRoutes): use new handlePreviewAdminForm controller fn
karrui Dec 14, 2020
6090ed5
feat(Form): replace util fn with form model method getPublicView
karrui Dec 17, 2020
b34b687
ref(FormModel): extract out common populate form params to constant
karrui Dec 17, 2020
66e23cb
test(FormModel): add tests for getPublicView instance method
karrui Dec 17, 2020
d273bc5
test(FormSvc): add tests for getFormPublicView
karrui Dec 17, 2020
872e3ca
test(AdminFormCtl): update tests to account for getFormPublicView step
karrui Dec 17, 2020
abd8de3
ref: rename form.util to form.utils
karrui Dec 17, 2020
90413be
Merge branch 'develop' into ref/preview-admin-form
karrui Dec 17, 2020
def39bb
feat(FormModel): revert getFullFormById to populate without selection
karrui Dec 21, 2020
b762046
Merge branch 'develop' into ref/preview-admin-form
karrui Dec 21, 2020
2ceb27f
Revert "test(AdminFormCtl): update tests to account for getFormPublic…
karrui Dec 21, 2020
d888023
feat(AgencyModel): add getPublicView instance method (and tests)
karrui Dec 21, 2020
e6fc2aa
feat(UserModel): add getPublicView instance method (and tests)
karrui Dec 21, 2020
0787c0e
feat(FormModel): update getPublicView to be non-async
karrui Dec 21, 2020
c67acf5
feat(FormSvc): remove getFormPublicView fn
karrui Dec 21, 2020
1d61893
Merge branch 'develop' into ref/preview-admin-form
karrui Feb 15, 2021
74bf9a1
ref(formUtils): use flatMap to transform email strings
karrui Feb 16, 2021
7754127
ref: use standard getPublicView interface across models
karrui Feb 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/app/models/agency.server.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Model, Mongoose, Schema } from 'mongoose'
import { pick } from 'lodash'
import { Mongoose, Schema } from 'mongoose'

import { IAgencySchema } from '../../types'
import { IAgencyModel, IAgencySchema, PublicAgency } from '../../types'

export const AGENCY_SCHEMA_ID = 'Agency'

// Exported for testing.
export const AGENCY_PUBLIC_FIELDS = [
'shortName',
'fullName',
'emailDomain',
'logo',
]

const AgencySchema = new Schema<IAgencySchema>(
{
shortName: {
Expand Down Expand Up @@ -40,7 +49,14 @@ const AgencySchema = new Schema<IAgencySchema>(
},
)

const compileAgencyModel = (db: Mongoose) =>
// Methods
AgencySchema.methods.getPublicView = function (
this: IAgencySchema,
): PublicAgency {
return pick(this, AGENCY_PUBLIC_FIELDS) as PublicAgency
}

const compileAgencyModel = (db: Mongoose): IAgencyModel =>
db.model<IAgencySchema>(AGENCY_SCHEMA_ID, AgencySchema)

/**
Expand All @@ -49,9 +65,9 @@ const compileAgencyModel = (db: Mongoose) =>
* @param db The mongoose instance to retrieve the Agency model from
* @returns The agency model
*/
const getAgencyModel = (db: Mongoose) => {
const getAgencyModel = (db: Mongoose): IAgencyModel => {
try {
return db.model(AGENCY_SCHEMA_ID) as Model<IAgencySchema>
return db.model(AGENCY_SCHEMA_ID) as IAgencyModel
} catch {
return compileAgencyModel(db)
}
Expand Down
39 changes: 35 additions & 4 deletions src/app/models/form.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import {
LogicType,
Permission,
PickDuplicateForm,
PublicForm,
PublicFormValues,
ResponseMode,
Status,
} from '../../types'
import { IPopulatedUser, IUserSchema } from '../../types/user'
import { MB } from '../constants/filesize'
import { OverrideProps } from '../modules/form/admin-form/admin-form.types'
import { transformEmails } from '../modules/form/form.util'
import { transformEmails } from '../modules/form/form.utils'
import { validateWebhookUrl } from '../modules/webhook/webhook.utils'

import getAgencyModel from './agency.server.model'
Expand Down Expand Up @@ -62,6 +64,23 @@ import getUserModel from './user.server.model'

export const FORM_SCHEMA_ID = 'Form'

// Exported for testing.
export const FORM_PUBLIC_FIELDS = [
'admin',
'authType',
'endPage',
'esrvcId',
'form_fields',
'form_logics',
'hasCaptcha',
'publicKey',
'startPage',
'status',
'title',
'_id',
'responseMode',
]

const bson = new BSON([
BSON.Binary,
BSON.Code,
Expand Down Expand Up @@ -401,6 +420,21 @@ const compileFormModel = (db: Mongoose): IFormModel => {
return { ...newForm, ...overrideProps }
}

FormSchema.methods.getPublicView = function (this: IFormSchema): PublicForm {
const basePublicView = pick(this, FORM_PUBLIC_FIELDS) as PublicFormValues

// Return non-populated public fields of form if not populated.
if (!this.populated('admin')) {
return basePublicView
}

// Populated, return public view with user's public view.
return {
...basePublicView,
admin: (this.admin as IUserSchema).getPublicView(),
}
}

// Archives form.
FormSchema.methods.archive = function (this: IFormSchema) {
// Return instantly when form is already archived.
Expand Down Expand Up @@ -463,11 +497,8 @@ const compileFormModel = (db: Mongoose): IFormModel => {
): Promise<IPopulatedForm | null> {
return this.findById(formId).populate({
path: 'admin',
// Remove irrelevant keys from populated fields of form admin and agency.
select: '-__v -created -lastModified -updatedAt -lastAccessed',
populate: {
path: 'agency',
select: '-__v -created -lastModified -updatedAt',
},
}) as Query<IPopulatedForm, IFormDocument>
}
Expand Down
18 changes: 17 additions & 1 deletion src/app/models/user.server.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { MongoError } from 'mongodb'
import { CallbackError, Mongoose, Schema } from 'mongoose'
import validator from 'validator'

import { IUser, IUserModel, IUserSchema } from '../../types'
import {
IAgencySchema,
IUser,
IUserModel,
IUserSchema,
PublicUser,
} from '../../types'

import getAgencyModel, { AGENCY_SCHEMA_ID } from './agency.server.model'

Expand Down Expand Up @@ -96,6 +102,16 @@ const compileUserModel = (db: Mongoose) => {
},
)

// Methods
UserSchema.methods.getPublicView = function (this: IUserSchema): PublicUser {
// Return public view of nested agency document if populated.
return {
agency: this.populated('agency')
? (this.agency as IAgencySchema).getPublicView()
: this.agency,
}
}

// Statics
/**
* Upserts given user details into User collection.
Expand Down
107 changes: 2 additions & 105 deletions src/app/modules/form/__tests__/form.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,10 @@
import { ObjectId } from 'bson-ext'
import { pick } from 'lodash'
import { Permission } from 'src/types'

import {
AuthType,
BasicField,
Colors,
FormLogoState,
IFieldSchema,
IPopulatedForm,
IPopulatedUser,
Permission,
ResponseMode,
Status,
} from 'src/types'

import {
getCollabEmailsWithPermission,
removePrivateDetailsFromForm,
} from '../form.utils'
import { getCollabEmailsWithPermission } from '../form.utils'

const MOCK_EMAIL_1 = '[email protected]'
const MOCK_EMAIL_2 = '[email protected]'

const MOCK_POPULATED_FORM = ({
startPage: {
colorTheme: Colors.Blue,
logo: { state: FormLogoState.Default },
estTimeTaken: 5,
paragraph: 'For testing.',
},
endPage: {
title: 'Thank you for submitting your declaration.',
buttonText: 'Submit another form',
buttonLink: '',
},
webhook: { url: '' },
emails: ['[email protected]'],
hasCaptcha: true,
authType: AuthType.NIL,
status: Status.Private,
inactiveMessage:
'If you think this is a mistake, please contact the agency that gave you the form link.',
isListed: true,
responseMode: ResponseMode.Email,
form_fields: [
{
title: 'Personal Particulars',
description: '',
required: true,
disabled: false,
fieldType: BasicField.Section,
_id: new ObjectId(),
globalId: '5DXH7jXlJRTBKVGimIMRilyxBfVjMe9myJqon0HzClS',
} as IFieldSchema,
],
form_logics: [],
permissionList: [],
_id: new ObjectId(),
admin: {
_id: new ObjectId(),
email: '[email protected]',
__v: 0,
agency: {
emailDomain: ['example.com'],
_id: new ObjectId(),
lastModified: new Date('2017-09-15T06:03:58.803Z'),
shortName: 'test',
fullName: 'Test Agency',
logo: 'path/to/nowhere',
created: new Date('2017-09-15T06:03:58.792Z'),
__v: 0,
},
created: new Date('2020-05-14T05:09:40.502Z'),
lastAccessed: new Date('2020-12-07T15:33:49.079Z'),
updatedAt: new Date('2020-12-08T02:52:27.637Z'),
} as IPopulatedUser,
title: 'test form',
created: new Date('2020-12-07T15:34:21.426Z'),
lastModified: new Date('2020-12-07T15:34:21.426Z'),
__v: 0,
} as unknown) as IPopulatedForm

describe('form.utils', () => {
describe('getCollabEmailsWithPermission', () => {
it('should return empty array when no arguments are given', () => {
Expand Down Expand Up @@ -118,31 +42,4 @@ describe('form.utils', () => {
expect(result).toEqual([MOCK_EMAIL_2])
})
})

describe('removePrivateDetailsFromForm', () => {
it('should correctly remove private details', async () => {
// Act
const actual = removePrivateDetailsFromForm(MOCK_POPULATED_FORM)

// Assert
expect(actual).toEqual({
...pick(MOCK_POPULATED_FORM, [
'admin',
'authType',
'endPage',
'esrvcId',
'form_fields',
'form_logics',
'hasCaptcha',
'publicKey',
'startPage',
'status',
'title',
'_id',
'responseMode',
]),
admin: pick(MOCK_POPULATED_FORM.admin, 'agency'),
})
})
})
})
Loading