Skip to content

Feat/bulk/all get job server #2092

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

Merged
merged 44 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2652197
feat: add sqs client and service
Oct 16, 2022
eb25c53
feat: create job and job items and sendSqsmessage in bulk controller
Oct 17, 2022
1bda696
chore: move qrCode job creation into JobController
gweiying Nov 15, 2022
7cc0a97
chore: generalize sqs message sending
gweiying Nov 15, 2022
0efbe33
chore: add sqs timeout
gweiying Nov 15, 2022
b5d1681
chore: fix missing region
gweiying Nov 15, 2022
9c4eae9
chore: create qrBatchSize and sqsRegion env variable
gweiying Nov 16, 2022
32c0237
chore: fix error logging and type
gweiying Nov 16, 2022
6f85b09
chore: fix bulk tests
gweiying Nov 16, 2022
ddef4d6
chore: fix lambda params
gweiying Nov 16, 2022
f9d6da8
chore: add JobItemId to job_items model for easy update
gweiying Nov 16, 2022
34291cf
chore: refactor logic to update jobItemStatus
gweiying Nov 16, 2022
415ec51
feat: expose endpoint to update jobItem
gweiying Nov 16, 2022
d4ec4d9
chore: use env var for lambda hash value
gweiying Nov 16, 2022
6bd61d4
chore: use http post request to send completion callback
gweiying Nov 16, 2022
3a648d8
chore: fix validation
gweiying Nov 16, 2022
b123d89
chore: add tests
gweiying Nov 16, 2022
1849bf1
chore: fix error catching for lambda
gweiying Nov 16, 2022
5824323
chore: add status to job table
gweiying Nov 18, 2022
68c8e91
chore: add logic to update job after job item callback
gweiying Nov 21, 2022
e02aeba
chore: fix tests
gweiying Nov 21, 2022
97482b8
chore: fix tests
gweiying Nov 21, 2022
4dc97fe
fix: call next to pass control
gweiying Nov 21, 2022
0ffcafd
fix: typo in docs
gweiying Nov 25, 2022
a070a60
chore: refactor job status computation logic
gweiying Nov 25, 2022
4238aa6
Merge branch 'feat/bulk/all' into feat/bulk/all-update-job
gweiying Nov 25, 2022
546637d
chore: fix lint errors after merging suggestions
gweiying Nov 25, 2022
9739842
chore: attach only jobId to req body in updateJobItem
gweiying Nov 25, 2022
a5cf113
chore: change enum from Failed to Failure
gweiying Nov 25, 2022
e16ad68
fix: add env variable to feature flag job creation
gweiying Nov 22, 2022
551801f
chore: add repository methods to retrieve jobs
gweiying Nov 22, 2022
ab0c75c
chore: add service methods for long polling and retrieving job inform…
gweiying Nov 22, 2022
b7aa651
chore: add endpoints for users to retrieve their latest job and updat…
gweiying Nov 22, 2022
81c40fc
chore: add tests
gweiying Nov 22, 2022
5e2f2df
fix: add env variables to docker-compose
gweiying Nov 22, 2022
e2e7e70
chore: add env variable for bulk download bucket
gweiying Nov 22, 2022
4e5d727
chore: fix tests
gweiying Nov 22, 2022
4481e9b
chore: fix typos in test
gweiying Nov 25, 2022
e4c729d
chore: fix tests
gweiying Nov 25, 2022
2e1b879
chore: change job status endpoint to get
gweiying Nov 25, 2022
381cb97
chore: rename jobItemIds to jobItemUrls
gweiying Nov 25, 2022
3756616
chore: update error statuses
gweiying Nov 25, 2022
558cc9a
Merge branch 'feat/bulk/all' into feat/bulk/all-get-job-server
gweiying Nov 28, 2022
0307492
chore: fix lint errors
gweiying Nov 28, 2022
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,14 @@ After these have been set up, set the environment variables according to the tab
|BULK_UPLOAD_MAX_NUM|No|Maximum number of links that can be bulk uploaded at once. Defaults to 1000|
|BULK_UPLOAD_RANDOM_STR_LENGTH|No|String length of randomly generated shortUrl in bulk upload. Defaults to 8|
|BULK_QR_CODE_BATCH_SIZE|No|Maximum batch size of QR codes to generate in a single Lambda run. Defaults to 1000|
|BULK_QR_CODE_BUCKET_URL|No|Link to download QR codes from|
|ACTIVATE_BULK_QR_CODE_GENERATION|No|Whether to start Lambda for bulk QR code generation or not. Defaults to false|
|REPLICA_URI|Yes|The postgres connection string, e.g. `postgres://postgres:postgres@postgres:5432/postgres`|
|SQS_BULK_QRCODE_GENERATE_START_URL|No|The SQS queue for starting QR code bulk generation Lambda|
|SQS_TIMEOUT|No|Duration of time in ms for sending to SQS queue before timeout. Defaults to 10000ms (10s)|
|SQS_REGION|No|AWS Region of SQS queue for starting QR code bulk generation Lambda|
|JOB_POLL_ATTEMPTS|No|Number of attempts for long polling of job status before timeout of 408 is returned. Defaults to 12|
|JOB_POLL_INTERVAL|No|Interval of time between attempts for long polling of job status in ms. Defaults to 5000ms (5s)|


#### Serverless functions for link migration
Expand Down
5 changes: 5 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ services:
- SQS_TIMEOUT=10000
- SQS_REGION=
- BULK_QR_CODE_BATCH_SIZE=1000
- LAMBDA_HASH_SECRET=
- ACTIVATE_BULK_QR_CODE_GENERATION=false
- JOB_POLL_ATTEMPTS=12
- JOB_POLL_INTERVAL=5000
- BULK_QR_CODE_BUCKET_URL=
volumes:
- ./public:/usr/src/gogovsg/public
- ./src:/usr/src/gogovsg/src
Expand Down
32 changes: 32 additions & 0 deletions src/server/api/callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Express from 'express'
import Joi from '@hapi/joi'
import { createValidator } from 'express-joi-validation'
import { container } from '../util/inversify'
import { JobController } from '../modules/job'
import { DependencyIds } from '../constants'

const router = Express.Router()
const validator = createValidator({ passError: true })

const jobController = container.get<JobController>(DependencyIds.jobController)

const jobItemCallbackSchema = Joi.object({
jobItemId: Joi.string().required(),
status: Joi.object()
.keys({
isSuccess: Joi.boolean().required(),
errorMessage: Joi.string().allow(null, ''),
})
.required(),
})
/**
* Update job status based on callback.
*/
router.post(
'/',
validator.body(jobItemCallbackSchema),
jobController.updateJobItem,
jobController.updateJob,
)

module.exports = router
29 changes: 28 additions & 1 deletion src/server/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Express from 'express'
import jsonMessage from '../util/json'
import { ERROR_404_PATH } from '../constants'
import { displayHostname } from '../config'
import { displayHostname, lambdaHashSecret } from '../config'
import assetVariant from '../../shared/util/asset-variant'

const router = Express.Router()
Expand Down Expand Up @@ -30,6 +30,31 @@ function userGuard(
next()
}

/**
* To protect lambda callback route. Temporary.
* */
const lambdaCallbackGuard = (
req: Express.Request,
res: Express.Response,
next: Express.NextFunction,
) => {
const authToken = req.headers.authorization
if (!authToken) {
res.unauthorized()
return
}
const [headerKey, key] = authToken.trim().split(' ')
if (
headerKey.toLowerCase() !== 'bearer' ||
!key ||
key !== lambdaHashSecret
) {
res.unauthorized()
return
}
next()
}

/**
* Preprocess request parameters.
* */
Expand All @@ -52,6 +77,8 @@ router.use('/link-stats', userGuard, require('./link-statistics'))
router.use('/link-audit', userGuard, require('./link-audit'))
router.use('/directory', userGuard, require('./directory'))

router.use('/callback', lambdaCallbackGuard, require('./callback'))

router.use((_, res) => {
res.status(404).render(ERROR_404_PATH, {
assetVariant,
Expand Down
9 changes: 9 additions & 0 deletions src/server/api/user/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '../../../shared/constants'
import {
ownershipTransferSchema,
pollJobInformationSchema,
tagRetrievalSchema,
urlBulkSchema,
urlEditSchema,
Expand Down Expand Up @@ -163,6 +164,14 @@ router.get(
userController.getTagsWithConditions,
)

router.get(
'/job/status',
validator.query(pollJobInformationSchema),
jobController.pollJobStatusUpdate,
)

router.get('/job/latest', jobController.getLatestJob)

router.get('/message', userController.getUserMessage)

router.get('/announcement', userController.getUserAnnouncement)
Expand Down
4 changes: 4 additions & 0 deletions src/server/api/user/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,7 @@ export const ownershipTransferSchema = Joi.object({
shortUrl: Joi.string().required(),
newUserEmail: Joi.string().required(),
})

export const pollJobInformationSchema = Joi.object({
jobId: Joi.number().required(),
})
8 changes: 8 additions & 0 deletions src/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,11 @@ export const bulkUploadRandomStrLength: number =
Number(process.env.BULK_UPLOAD_RANDOM_STR_LENGTH) || 8
export const qrCodeJobBatchSize: number =
Number(process.env.BULK_QR_CODE_BATCH_SIZE) || 1000
export const qrCodeBucketUrl: string = process.env.BULK_QR_CODE_BUCKET_URL || ''
export const shouldGenerateQRCodes: boolean =
process.env.ACTIVATE_BULK_QR_CODE_GENERATION === 'true'
export const lambdaHashSecret: string = process.env.LAMBDA_HASH_SECRET as string
export const jobPollInterval: number =
Number(process.env.JOB_POLL_INTERVAL) || 5000 // in ms
export const jobPollAttempts: number =
Number(process.env.JOB_POLL_ATTEMPTS) || 12
2 changes: 1 addition & 1 deletion src/server/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import { FileCheckController, UrlCheckController } from './modules/threat'
import { QrCodeService } from './modules/qr/services'
import { QrCodeController } from './modules/qr'
import TagManagementService from './modules/user/services/TagManagementService'
import JobManagementService from './modules/job/services/JobManagementService'
import { JobManagementService } from './modules/job/services'
import { BulkService } from './modules/bulk/services'
import { BulkController } from './modules/bulk'
import { SQSService } from './services/sqs'
Expand Down
21 changes: 18 additions & 3 deletions src/server/models/job.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Sequelize from 'sequelize'
import { IdType } from '../../types/server/models'
import { JobItemStatusEnum } from '../repositories/enums'
import { JobItemStatusEnum, JobStatusEnum } from '../repositories/enums'
import { sequelize } from '../util/sequelize'

export interface JobType extends IdType, Sequelize.Model {
readonly uuid: string
readonly userId: Number
readonly createdAt: string
readonly updatedAt: string
readonly status: JobStatusEnum
}

type JobStatic = typeof Sequelize.Model & {
Expand All @@ -22,6 +23,16 @@ export const Job = <JobStatic>sequelize.define(
defaultValue: Sequelize.UUIDV4,
unique: true,
},
status: {
type: Sequelize.ENUM,
values: [
JobStatusEnum.InProgress,
JobStatusEnum.Success,
JobStatusEnum.Failure,
],
defaultValue: JobItemStatusEnum.InProgress,
allowNull: false,
},
},
{
defaultScope: {
Expand All @@ -35,6 +46,7 @@ export interface JobItemType extends IdType, Sequelize.Model {
readonly message: string
readonly params: JSON
readonly jobId: number
readonly jobItemId: string
readonly createdAt: string
readonly updatedAt: string
}
Expand All @@ -51,20 +63,23 @@ export const JobItem = <JobItemStatic>sequelize.define(
values: [
JobItemStatusEnum.InProgress,
JobItemStatusEnum.Success,
JobItemStatusEnum.Failed,
JobItemStatusEnum.Failure,
],
defaultValue: JobItemStatusEnum.InProgress,
allowNull: false,
},
message: {
type: Sequelize.STRING,
defaultValue: '',
allowNull: false,
},
params: {
type: Sequelize.JSON,
allowNull: false,
},
jobItemId: {
type: Sequelize.STRING,
allowNull: false,
},
},
{
defaultScope: {
Expand Down
17 changes: 12 additions & 5 deletions src/server/modules/bulk/BulkController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DependencyIds } from '../../constants'
import { BulkService } from './interfaces'
import { UrlManagementService } from '../user/interfaces'
import dogstatsd from '../../util/dogstatsd'
import { logger, shouldGenerateQRCodes } from '../../config'

@injectable()
export class BulkController {
Expand Down Expand Up @@ -62,12 +63,18 @@ export class BulkController {
return
}

// put jobParamsList on the req body so that it can be used by JobController
req.body.jobParamsList = urlMappings

dogstatsd.increment('bulk.hash.success', 1, 1)
res.ok(jsonMessage(`${urlMappings.length} links created`))
next()
if (shouldGenerateQRCodes) {
logger.info('shouldGenerateQRCodes true, triggering QR code generation')
// put jobParamsList on the req body so that it can be used by JobController
req.body.jobParamsList = urlMappings
next()
} else {
logger.info(
'shouldGenerateQRCodes false, not triggering QR code generation',
)
res.ok({ count: urlMappings.length })
}
}
}

Expand Down
Loading