Skip to content

Commit e615e66

Browse files
feat(Statements): Add statement processing priority flag (#824)
2 parents 3243fd4 + 7c824c1 commit e615e66

29 files changed

+356
-52
lines changed

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,10 @@ EXPRESS_PORT=8081
8282
#WINSTON_CLOUDWATCH_ACCESS_KEY_ID=
8383
#WINSTON_CLOUDWATCH_SECRET_ACCESS_KEY=
8484
#WINSTON_CLOUDWATCH_REGION=
85+
86+
###############################
87+
# Statement handling priority #
88+
###############################
89+
90+
# Uncomment next line if you want to enable statement handling priority
91+
#ENABLE_QUEUE_PRIORITY=true
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum StatementProcessingPriority {
2+
LOW = 'LOW',
3+
MEDIUM = 'MEDIUM',
4+
}

src/apps/statements/expressPresenter/postStatements/alternateRequest.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
22
import { defaultTo, get } from 'lodash';
33
import { parse as parseQueryString } from 'query-string';
44
import streamToString from 'stream-to-string';
5+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
56
import InvalidContentType from '../../errors/InvalidContentType';
67
import InvalidMethod from '../../errors/InvalidMethod';
78
import parseJson from '../../utils/parseJson';
@@ -13,6 +14,7 @@ import getStatements from '../utils/getStatements';
1314
import getUrlPath from '../utils/getUrlPath';
1415
import storeStatement from '../utils/storeStatement';
1516
import validateVersionHeader from '../utils/validateHeaderVersion';
17+
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
1618
import storeStatements from './storeStatements';
1719

1820
export interface Options {
@@ -24,15 +26,16 @@ export interface Options {
2426

2527
const checkContentType = (bodyParams: any) => {
2628
const contentType = get(bodyParams, 'Content-Type', 'application/json');
29+
2730
if (!jsonContentTypePattern.test(contentType)) {
2831
throw new InvalidContentType(contentType);
2932
}
3033
};
3134

3235
const getBodyContent = (bodyParams: any) => {
3336
const unparsedBody = get(bodyParams, 'content', '');
34-
const body = parseJson(unparsedBody, ['body', 'content']);
35-
return body;
37+
38+
return parseJson(unparsedBody, ['body', 'content']);
3639
};
3740

3841
const getHeader = (bodyParams: any, req: Request, name: string): string => {
@@ -41,22 +44,31 @@ const getHeader = (bodyParams: any, req: Request, name: string): string => {
4144

4245
const getBodyParams = async (stream: NodeJS.ReadableStream) => {
4346
const body = await streamToString(stream);
44-
const decodedBody = parseQueryString(body);
45-
return decodedBody;
47+
48+
return parseQueryString(body);
4649
};
4750

4851
export default async ({ config, method, req, res }: Options) => {
4952
checkUnknownParams(req.query, ['method']);
5053

54+
validateStatementProcessingPriority(req.query.priority as string | undefined);
55+
const priority =
56+
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
57+
5158
if (method === 'POST' || (method === undefined && config.allowUndefinedMethod)) {
5259
const bodyParams = await getBodyParams(req);
60+
5361
checkContentType(bodyParams);
62+
5463
const auth = getHeader(bodyParams, req, 'Authorization');
5564
const client = await getClient(config, auth);
5665
const version = getHeader(bodyParams, req, 'X-Experience-API-Version');
66+
5767
validateVersionHeader(version);
68+
5869
const body = getBodyContent(bodyParams);
59-
return storeStatements({ config, client, body, attachments: [], res });
70+
71+
return storeStatements({ config, client, priority, body, attachments: [], res });
6072
}
6173

6274
if (method === 'GET') {
@@ -65,22 +77,30 @@ export default async ({ config, method, req, res }: Options) => {
6577
const auth = getHeader(bodyParams, req, 'Authorization');
6678
const client = await getClient(config, auth);
6779
const version = getHeader(bodyParams, req, 'X-Experience-API-Version');
80+
6881
validateVersionHeader(version);
82+
6983
const acceptedLangs = defaultTo<string>(req.header('Accept-Language'), '');
7084
const queryParams = bodyParams;
85+
7186
return getStatements({ config, res, client, queryParams, urlPath, acceptedLangs });
7287
}
7388

7489
if (method === 'PUT') {
7590
const bodyParams = await getBodyParams(req);
91+
7692
checkContentType(bodyParams);
93+
7794
const auth = getHeader(bodyParams, req, 'Authorization');
7895
const client = await getClient(config, auth);
7996
const version = getHeader(bodyParams, req, 'X-Experience-API-Version');
97+
8098
validateVersionHeader(version);
99+
81100
const body = getBodyContent(bodyParams);
82-
const queryParams = bodyParams;
83-
return storeStatement({ config, client, body, attachments: [], queryParams, res });
101+
const statementId = bodyParams.statementId as string | undefined;
102+
103+
return storeStatement({ config, client, body, priority, attachments: [], statementId, res });
84104
}
85105

86106
throw new InvalidMethod(method);

src/apps/statements/expressPresenter/postStatements/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Request, Response } from 'express';
22
import { defaultTo } from 'lodash';
33
import { parse as parseQueryString } from 'query-string';
44
import streamToString from 'stream-to-string';
5+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
56
import InvalidContentType from '../../errors/InvalidContentType';
67
import parseJson from '../../utils/parseJson';
78
import Config from '../Config';
@@ -13,6 +14,7 @@ import {
1314
} from '../utils/contentTypePatterns';
1415
import getClient from '../utils/getClient';
1516
import validateVersionHeader from '../utils/validateHeaderVersion';
17+
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
1618
import alternateRequest from './alternateRequest';
1719
import storeStatements from './storeStatements';
1820
import storeWithAttachments from './storeWithAttachments';
@@ -34,6 +36,11 @@ export default (config: Config) => {
3436
config,
3537
async (req: Request, res: Response): Promise<void> => {
3638
const method = req.query.method as string | undefined;
39+
40+
validateStatementProcessingPriority(req.query.priority as string | undefined);
41+
42+
const priority =
43+
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
3744
const contentType = defaultTo(req.header('Content-Type'), '');
3845

3946
if (method === undefined && multipartContentTypePattern.test(contentType)) {
@@ -46,7 +53,7 @@ export default (config: Config) => {
4653

4754
const body = await parseJsonBody(config, req);
4855
const attachments: any[] = [];
49-
return storeStatements({ config, client, body, attachments, res });
56+
return storeStatements({ config, client, priority, body, attachments, res });
5057
}
5158

5259
if (method !== undefined || alternateContentTypePattern.test(contentType)) {

src/apps/statements/expressPresenter/postStatements/storeStatements.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import { Response } from 'express';
22
import { StatusCodes } from 'http-status-codes';
33
import { isArray } from 'lodash';
4+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
45
import ClientModel from '../../models/ClientModel';
56
import { xapiHeaderVersion } from '../../utils/constants';
67
import Config from '../Config';
78

89
export interface Options {
910
readonly config: Config;
1011
readonly client: ClientModel;
12+
readonly priority: StatementProcessingPriority;
1113
readonly body: any;
1214
readonly attachments: any[];
1315
readonly res: Response;
1416
}
1517

16-
export default async ({ config, client, body, attachments, res }: Options) => {
18+
export default async ({ config, client, priority, body, attachments, res }: Options) => {
1719
const models = isArray(body) ? body : [body];
18-
const ids = await config.service.storeStatements({ models, attachments, client });
20+
const ids = await config.service.storeStatements({ priority, models, attachments, client });
1921
res.setHeader('X-Experience-API-Version', xapiHeaderVersion);
2022
res.status(StatusCodes.OK);
2123
res.json(ids);

src/apps/statements/expressPresenter/postStatements/storeWithAttachments.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Request, Response } from 'express';
22
import { defaultTo } from 'lodash';
3+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
34
import Config from '../Config';
45
import getClient from '../utils/getClient';
56
import getMultipartStatements from '../utils/getMultipartStatements';
7+
import { validateStatementProcessingPriority } from '../utils/validateStatementProcessingPriority';
68
import storeStatements from './storeStatements';
79

810
export interface Options {
@@ -13,6 +15,12 @@ export interface Options {
1315

1416
export default async ({ config, req, res }: Options) => {
1517
const client = await getClient(config, defaultTo(req.header('Authorization'), ''));
18+
19+
validateStatementProcessingPriority(req.query.priority as string | undefined);
20+
21+
const priority =
22+
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
1623
const { body, attachments } = await getMultipartStatements(req);
17-
return storeStatements({ config, client, body, attachments, res });
24+
25+
return storeStatements({ config, client, priority, body, attachments, res });
1826
};

src/apps/statements/expressPresenter/putStatement.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Request, Response } from 'express';
22
import { defaultTo } from 'lodash';
33
import streamToString from 'stream-to-string';
4+
import { StatementProcessingPriority } from '../enums/statementProcessingPriority.enum';
45
import InvalidContentType from '../errors/InvalidContentType';
56
import AttachmentModel from '../models/AttachmentModel';
67
import parseJson from '../utils/parseJson';
@@ -11,26 +12,30 @@ import getClient from './utils/getClient';
1112
import getMultipartStatements from './utils/getMultipartStatements';
1213
import storeStatement from './utils/storeStatement';
1314
import validateVersionHeader from './utils/validateHeaderVersion';
15+
import { validateStatementProcessingPriority } from './utils/validateStatementProcessingPriority';
1416

1517
export default (config: Config) => {
1618
return catchErrors(
1719
config,
1820
async (req: Request, res: Response): Promise<void> => {
19-
const contentType = defaultTo(req.header('Content-Type'), '');
20-
const client = await getClient(config, defaultTo(req.header('Authorization'), ''));
21+
validateStatementProcessingPriority(req.query.priority as string | undefined);
2122
validateVersionHeader(req.header('X-Experience-API-Version'));
2223

23-
const queryParams = req.query;
24+
const contentType = defaultTo(req.header('Content-Type'), '');
25+
const client = await getClient(config, defaultTo(req.header('Authorization'), ''));
26+
const priority =
27+
(req.query.priority as StatementProcessingPriority) || StatementProcessingPriority.MEDIUM;
28+
const statementId = req.query.statementId as string;
2429

2530
if (multipartContentTypePattern.test(contentType)) {
2631
const { body, attachments } = await getMultipartStatements(req);
27-
return storeStatement({ config, body, attachments, client, queryParams, res });
32+
return storeStatement({ config, priority, body, attachments, client, statementId, res });
2833
}
2934

3035
if (jsonContentTypePattern.test(contentType)) {
3136
const body = parseJson(await streamToString(req), ['body']);
3237
const attachments: AttachmentModel[] = [];
33-
return storeStatement({ config, body, attachments, client, queryParams, res });
38+
return storeStatement({ config, priority, body, attachments, client, statementId, res });
3439
}
3540

3641
throw new InvalidContentType(contentType);
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Response } from 'express';
22
import { StatusCodes } from 'http-status-codes';
3+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
34
import MissingStatementId from '../../errors/MissingStatementId';
45
import UnequalStatementId from '../../errors/UnequalStatementId';
56
import AttachmentModel from '../../models/AttachmentModel';
@@ -9,29 +10,42 @@ import Config from '../Config';
910

1011
export interface Options {
1112
readonly config: Config;
13+
readonly priority: StatementProcessingPriority;
1214
readonly body: any;
1315
readonly attachments: AttachmentModel[];
1416
readonly client: ClientModel;
15-
readonly queryParams: any;
17+
readonly statementId?: string;
1618
readonly res: Response;
1719
}
1820

19-
export default async ({ config, body, attachments, client, queryParams, res }: Options) => {
20-
const statementId = queryParams.statementId;
21+
export default async ({
22+
config,
23+
priority,
24+
body,
25+
attachments,
26+
client,
27+
statementId,
28+
res,
29+
}: Options) => {
2130
if (statementId === undefined) {
2231
throw new MissingStatementId();
2332
}
33+
2434
if (body.id !== undefined && body.id !== statementId) {
2535
throw new UnequalStatementId(statementId);
2636
}
37+
2738
const models = [
2839
{
2940
...body,
3041
id: statementId, // Ensures the id is set to the given id.
3142
},
3243
];
33-
await config.service.storeStatements({ models, attachments, client });
44+
45+
await config.service.storeStatements({ priority, models, attachments, client });
46+
3447
res.setHeader('X-Experience-API-Version', xapiHeaderVersion);
3548
res.status(StatusCodes.NO_CONTENT);
49+
3650
res.send();
3751
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createWarning, Warnings } from 'rulr';
2+
3+
import { StatementProcessingPriority } from '../../enums/statementProcessingPriority.enum';
4+
5+
export const validateStatementProcessingPriority = (
6+
statementProcessingPriority: string | undefined,
7+
): void | Warnings[] => {
8+
if (
9+
statementProcessingPriority &&
10+
!Object.values(StatementProcessingPriority).includes(
11+
statementProcessingPriority as StatementProcessingPriority,
12+
)
13+
) {
14+
const warnings = [createWarning(statementProcessingPriority, ['query', 'priority'])];
15+
throw new Warnings({}, ['query'], warnings);
16+
}
17+
};

src/apps/statements/models/UnstoredStatementModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { StatementProcessingPriority } from '../enums/statementProcessingPriority.enum';
12
import Statement from './Statement';
23

34
export interface Ref {
@@ -9,6 +10,7 @@ interface UnstoredStatementModel {
910
readonly organisation: string;
1011
readonly client: string;
1112
readonly lrs_id: string;
13+
readonly priority: StatementProcessingPriority;
1214
readonly person: string | null;
1315
readonly active: boolean;
1416
readonly voided: boolean;

0 commit comments

Comments
 (0)