Skip to content

Commit

Permalink
Merge pull request #517 from opengovsg/release-4.41.0
Browse files Browse the repository at this point in the history
build: merge Release 4.41.0 into master
  • Loading branch information
mantariksh committed Oct 27, 2020
2 parents 42238be + 058ab66 commit cf80dc0
Show file tree
Hide file tree
Showing 44 changed files with 2,338 additions and 1,287 deletions.
595 changes: 311 additions & 284 deletions CHANGELOG.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Dockerfile.development
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ WORKDIR /opt/formsg

ENV CHROMIUM_BIN=/usr/bin/chromium-browser
ENV NODE_ENV=development
ENV NODE_OPTIONS=--max-old-space-size=2048
RUN apk update && apk upgrade && \
# Build dependencies for node_modules
apk add --virtual native-deps \
Expand Down
1,597 changes: 887 additions & 710 deletions package-lock.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "FormSG",
"description": "Form Manager for Government",
"version": "4.40.0",
"version": "4.41.0",
"homepage": "https://form.gov.sg",
"authors": [
"FormSG <formsg@data.gov.sg>"
Expand Down Expand Up @@ -65,7 +65,7 @@
"@opengovsg/angular-legacy-sortablejs-maintained": "^1.0.0",
"@opengovsg/angular-recaptcha-fallback": "^5.0.0",
"@opengovsg/formsg-sdk": "0.8.2",
"@opengovsg/myinfo-gov-client": "^1.0.4",
"@opengovsg/myinfo-gov-client": "^2.0.0",
"@opengovsg/ng-file-upload": "^12.2.14",
"@opengovsg/spcp-auth-client": "^1.3.5",
"@sentry/browser": "^5.24.2",
Expand All @@ -77,10 +77,10 @@
"angular-aria": "^1.8.0",
"angular-cookies": "~1.8.1",
"angular-drag-scroll": "^0.2.1",
"angular-messages": "^1.8.0",
"angular-messages": "^1.8.1",
"angular-moment": "~1.2.0",
"angular-permission": "~1.1.1",
"angular-resource": "^1.8.0",
"angular-resource": "^1.8.1",
"angular-sanitize": "^1.8.0",
"angular-translate": "^2.18.2",
"angular-translate-loader-partial": "^2.18.3",
Expand All @@ -95,7 +95,7 @@
"body-parser": "^1.18.3",
"bootstrap": "3.4.1",
"boxicons": "1.8.0",
"bson-ext": "^2.0.3",
"bson-ext": "^2.0.5",
"busboy": "^0.3.1",
"celebrate": "^13.0.3",
"compression": "~1.7.2",
Expand Down Expand Up @@ -155,7 +155,7 @@
"ui-select": "^0.19.8",
"uid-generator": "^2.0.0",
"uuid": "^8.3.0",
"validator": "^13.1.1",
"validator": "^13.1.17",
"web-streams-polyfill": "^2.1.1",
"whatwg-fetch": "^3.4.1",
"winston": "^3.3.3",
Expand Down Expand Up @@ -215,12 +215,12 @@
"google-fonts-plugin": "4.1.0",
"html-loader": "~0.5.5",
"htmlhint": "^0.14.1",
"husky": "^4.2.5",
"husky": "^4.3.0",
"jasmine": "^3.6.1",
"jasmine-core": "^3.1.0",
"jasmine-sinon": "^0.4.0",
"jasmine-spec-reporter": "^5.0.2",
"jest": "^26.4.2",
"jasmine-spec-reporter": "^6.0.0",
"jest": "^26.5.3",
"lint-staged": "^10.4.0",
"maildev": "^1.1.0",
"mini-css-extract-plugin": "^0.5.0",
Expand All @@ -237,7 +237,7 @@
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^20.0.0",
"stylelint-prettier": "^1.1.2",
"supertest": "^3.3.0",
"supertest": "^5.0.0",
"supertest-session": "^4.1.0",
"terser-webpack-plugin": "^1.2.3",
"testcafe": "^1.8.6",
Expand Down
116 changes: 11 additions & 105 deletions src/app/controllers/admin-console.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@
/**
* Module dependencies.
*/
const _ = require('lodash')
const mongoose = require('mongoose')
const moment = require('moment-timezone')
const { StatusCodes } = require('http-status-codes')

const getLoginModel = require('../models/login.server.model').default
const getSubmissionModel = require('../models/submission.server.model').default
const getFormStatisticsTotalModel = require('../models/form_statistics_total.server.model')
.default
const getFormModel = require('../models/form.server.model').default
const { createReqMeta } = require('../utils/request')

const Login = getLoginModel(mongoose)
const Submission = getSubmissionModel(mongoose)
const FormStatisticsTotal = getFormStatisticsTotalModel(mongoose)
const Form = getFormModel(mongoose)
const _ = require('lodash')

const logger = require('../../config/logger').createLoggerWithLabel(module)
const { createReqMeta } = require('../utils/request')

// Examples search-specific constants
const PAGE_SIZE = 16 // maximum number of results to return
Expand Down Expand Up @@ -548,8 +546,15 @@ const getExampleFormsUsing = (
})
}

mongoQuery.catch((err) => {
return cb(err, StatusCodes.INTERNAL_SERVER_ERROR, {
mongoQuery.catch((error) => {
logger.error({
message: 'Error with Examples Search query',
meta: {
action: 'getExampleFormsUsing',
},
error,
})
return cb(error, StatusCodes.INTERNAL_SERVER_ERROR, {
message: 'Error in retrieving example forms.',
})
})
Expand Down Expand Up @@ -729,102 +734,3 @@ exports.getSingleExampleFormUsingAggregateCollection = function (req, res) {
},
)
}

exports.getLoginStats = function (req, res) {
let { yr, mth, esrvcId } = req.query
let year = parseInt(yr)
let month = parseInt(mth)

const startOfMonth = moment
.tz([year, month], 'Asia/Singapore')
.startOf('month')
const endOfMonth = moment(startOfMonth).endOf('month')
Login.aggregate(
[
{
$match: {
esrvcId,
created: {
$gte: startOfMonth.toDate(),
$lte: endOfMonth.toDate(),
},
},
},
{
$group: {
_id: {
form: '$form',
admin: '$admin',
authType: '$authType',
},
total: { $sum: 1 },
},
},
{
$lookup: {
from: 'users',
localField: '_id.admin',
foreignField: '_id',
as: 'userInfo',
},
},
{
$unwind: '$userInfo',
},
{
$lookup: {
from: 'forms',
localField: '_id.form',
foreignField: '_id',
as: 'formInfo',
},
},
{
$unwind: '$formInfo',
},
{
$project: {
_id: 0,
adminEmail: '$userInfo.email',
formName: '$formInfo.title',
total: '$total',
formId: '$_id.form',
authType: '$_id.authType',
},
},
],
function (error, loginStats) {
if (error) {
logger.error({
message: 'Failed to retrieve billing records',
meta: {
action: 'getLoginStats',
...createReqMeta(req),
},
error,
})
return res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ message: 'Error in retrieving billing records' })
} else if (!loginStats) {
return res
.status(StatusCodes.NOT_FOUND)
.json({ message: 'No billing records found' })
} else {
logger.info({
message: `Billing search for ${esrvcId} by ${
req.session.user && req.session.user.email
}`,
meta: {
action: 'getLoginStats',
...createReqMeta(req),
},
})

return res.json({
loginStats,
})
}
},
)
}
4 changes: 2 additions & 2 deletions src/app/controllers/admin-forms.server.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ function makeModule(connection) {
if (!VALID_UPLOAD_FILE_TYPES.includes(req.body.fileType)) {
return res
.status(StatusCodes.BAD_REQUEST)
.send(`Your file type "${req.body.fileType}" is not supported`)
.json(`Your file type "${req.body.fileType}" is not supported`)
}

s3.createPresignedPost(
Expand Down Expand Up @@ -710,7 +710,7 @@ function makeModule(connection) {
if (!VALID_UPLOAD_FILE_TYPES.includes(req.body.fileType)) {
return res
.status(StatusCodes.BAD_REQUEST)
.send(`Your file type "${req.body.fileType}" is not supported`)
.json(`Your file type "${req.body.fileType}" is not supported`)
}

s3.createPresignedPost(
Expand Down
6 changes: 0 additions & 6 deletions src/app/factories/spcp-myinfo.factory.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const adminConsole = require('../controllers/admin-console.server.controller')
const spcp = require('../controllers/spcp.server.controller')
const myInfo = require('../controllers/myinfo.server.controller')
const admin = require('../controllers/admin-forms.server.controller')
Expand Down Expand Up @@ -111,7 +110,6 @@ const spcpFactory = ({ isEnabled, props }) => {
appendVerifiedSPCPResponses: spcp.appendVerifiedSPCPResponses,
encryptedVerifiedFields: spcp.encryptedVerifiedFields,
passThroughSpcp: admin.passThroughSpcp,
getLoginStats: adminConsole.getLoginStats,
verifyMyInfoVals: myInfo.verifyMyInfoVals,
returnSpcpRedirectURL: spcp.returnSpcpRedirectURL,
singPassLogin: spcp.singPassLogin(ndiConfig),
Expand All @@ -132,10 +130,6 @@ const spcpFactory = ({ isEnabled, props }) => {
appendVerifiedSPCPResponses: (req, res, next) => next(),
encryptedVerifiedFields: (req, res, next) => next(),
passThroughSpcp: (req, res, next) => next(),
getLoginStats: (req, res) =>
res.json({
loginStats: [],
}),
verifyMyInfoVals: (req, res, next) => next(),
returnSpcpRedirectURL: (req, res) =>
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: errMsg }),
Expand Down
76 changes: 71 additions & 5 deletions src/app/models/login.server.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Model, Mongoose, Schema } from 'mongoose'
import { Mongoose, Schema } from 'mongoose'

import { AuthType, ILoginSchema } from '../../types'
import {
AuthType,
ILoginModel,
ILoginSchema,
LoginStatistic,
} from '../../types'

import { AGENCY_SCHEMA_ID } from './agency.server.model'
import { FORM_SCHEMA_ID } from './form.server.model'
Expand Down Expand Up @@ -47,18 +52,79 @@ const LoginSchema = new Schema<ILoginSchema>(
},
)

LoginSchema.statics.aggregateLoginStats = function (
this: ILoginModel,
esrvcId: string,
gte: Date,
lte: Date,
): Promise<LoginStatistic[]> {
return this.aggregate<LoginStatistic>([
{
$match: {
esrvcId,
created: {
$gte: gte,
$lte: lte,
},
},
},
{
$group: {
_id: {
form: '$form',
admin: '$admin',
authType: '$authType',
},
total: { $sum: 1 },
},
},
{
$lookup: {
from: 'users',
localField: '_id.admin',
foreignField: '_id',
as: 'userInfo',
},
},
{
$unwind: '$userInfo',
},
{
$lookup: {
from: 'forms',
localField: '_id.form',
foreignField: '_id',
as: 'formInfo',
},
},
{
$unwind: '$formInfo',
},
{
$project: {
_id: 0,
adminEmail: '$userInfo.email',
formName: '$formInfo.title',
total: '$total',
formId: '$_id.form',
authType: '$_id.authType',
},
},
]).exec()
}

const compileLoginModel = (db: Mongoose) =>
db.model<ILoginSchema>(LOGIN_SCHEMA_ID, LoginSchema)
db.model<ILoginSchema>(LOGIN_SCHEMA_ID, LoginSchema) as ILoginModel

/**
* Retrieves the Login model on the given Mongoose instance. If the model is
* not registered yet, the model will be registered and returned.
* @param db The mongoose instance to retrieve the Login model from
* @returns The login model
*/
const getLoginModel = (db: Mongoose) => {
const getLoginModel = (db: Mongoose): ILoginModel => {
try {
return db.model(LOGIN_SCHEMA_ID) as Model<ILoginSchema>
return db.model(LOGIN_SCHEMA_ID) as ILoginModel
} catch {
return compileLoginModel(db)
}
Expand Down
Loading

0 comments on commit cf80dc0

Please sign in to comment.