Skip to content

Commit 868d333

Browse files
authored
Merge pull request #30 from UN-OCHA/HPC-7904-models
Define models needed for HPC-7904
2 parents 620331c + ff4df88 commit 868d333

17 files changed

+172
-28
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
/index.d.ts
55
/yarn-error.log
66
package-lock.json
7+
8+
# IDE artifacts
9+
.vscode

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@unocha/hpc-api-core",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Core libraries supporting HPC.Tools API Backend",
55
"license": "Apache-2.0",
66
"private": false,
@@ -28,7 +28,7 @@
2828
"husky": "^7.0.2",
2929
"lint-staged": "^11.2.4",
3030
"prettier": "2.4.1",
31-
"typescript": "^4.3.5"
31+
"typescript": "^4.4.4"
3232
},
3333
"lint-staged": {
3434
"*.{ts,js}": [

src/db/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,22 @@ import expiredData from './models/expiredData';
1414
import form from './models/form';
1515
import governingEntity from './models/governingEntity';
1616
import governingEntityVersion from './models/governingEntityVersion';
17+
import location from './models/location';
1718
import operation from './models/operation';
1819
import operationCluster from './models/operationCluster';
1920
import participant from './models/participant';
20-
import project from './models/project';
2121
import plan from './models/plan';
2222
import planEntity from './models/planEntity';
2323
import planEntityVersion from './models/planEntityVersion';
24+
import planVersion from './models/planVersion';
25+
import planYear from './models/planYear';
26+
import project from './models/project';
2427
import projectVersion from './models/projectVersion';
2528
import projectVersionAttachment from './models/projectVersionAttachment';
2629
import projectVersionPlan from './models/projectVersionPlan';
2730
import reportingWindow from './models/reportingWindow';
2831
import reportingWindowAssignment from './models/reportingWindowAssignment';
32+
import usageYear from './models/usageYear';
2933
import workflowStatusOption from './models/workflowStatusOption';
3034

3135
export default (conn: Knex) => ({
@@ -44,17 +48,21 @@ export default (conn: Knex) => ({
4448
form: form(conn),
4549
governingEntity: governingEntity(conn),
4650
governingEntityVersion: governingEntityVersion(conn),
51+
location: location(conn),
4752
operation: operation(conn),
4853
operationCluster: operationCluster(conn),
4954
participant: participant(conn),
5055
plan: plan(conn),
5156
planEntity: planEntity(conn),
5257
planEntityVersion: planEntityVersion(conn),
58+
planVersion: planVersion(conn),
59+
planYear: planYear(conn),
5360
project: project(conn),
5461
projectVersion: projectVersion(conn),
5562
projectVersionAttachment: projectVersionAttachment(conn),
5663
projectVersionPlan: projectVersionPlan(conn),
5764
reportingWindow: reportingWindow(conn),
5865
reportingWindowAssignment: reportingWindowAssignment(conn),
66+
usageYear: usageYear(conn),
5967
workflowStatusOption: workflowStatusOption(conn),
6068
});

src/db/models/attachmentPrototype.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { LOCALIZED_STRING } from '../util/datatypes';
56
import { defineIDModel } from '../util/id-model';
67
import { PLAN_ID } from './plan';
78

@@ -26,10 +27,6 @@ export const ATTACHMENT_TYPE = t.keyof({
2627
});
2728
export type AttachmentType = t.TypeOf<typeof ATTACHMENT_TYPE>;
2829

29-
const LOCALIZED_STRING = t.type({
30-
en: t.string,
31-
});
32-
3330
const FIELDS = t.array(
3431
t.type({
3532
name: LOCALIZED_STRING,
@@ -59,10 +56,8 @@ export default defineIDModel({
5956
generated: {
6057
id: { kind: 'branded-integer', brand: ATTACHMENT_PROTOTYPE_ID },
6158
},
62-
optional: {
63-
planId: { kind: 'branded-integer', brand: PLAN_ID },
64-
},
6559
accidentallyOptional: {
60+
planId: { kind: 'branded-integer', brand: PLAN_ID },
6661
refCode: { kind: 'checked', type: t.string },
6762
type: { kind: 'checked', type: ATTACHMENT_TYPE },
6863
value: { kind: 'checked', type: ATTACHMENT_PROTOTYPE_VALUE },

src/db/models/entityPrototype.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ export default defineIDModel({
4444
generated: {
4545
id: { kind: 'branded-integer', brand: ENTITY_PROTOTYPE_ID },
4646
},
47+
optional: { orderNumber: { kind: 'checked', type: t.number } },
4748
accidentallyOptional: {
4849
refCode: { kind: 'checked', type: ENTITY_PROTOTYPE_REF_CODE },
4950
type: { kind: 'checked', type: ENTITY_PROTOTYPE_TYPE },
5051
planId: { kind: 'checked', type: PLAN_ID },
51-
orderNumber: { kind: 'checked', type: t.number },
5252
value: { kind: 'checked', type: t.unknown },
5353
},
5454
},

src/db/models/governingEntityVersion.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default defineLegacyVersionedModel({
2525
brand: GOVERNING_ENTITY_VERSION_ID,
2626
},
2727
},
28+
optional: { tags: { kind: 'checked', type: t.array(t.string) } },
2829
accidentallyOptional: {
2930
governingEntityId: {
3031
kind: 'branded-integer',
@@ -33,9 +34,8 @@ export default defineLegacyVersionedModel({
3334
name: { kind: 'checked', type: t.string },
3435
customReference: { kind: 'checked', type: t.string },
3536
value: { kind: 'checked', type: t.unknown },
36-
tags: { kind: 'checked', type: t.array(t.string) },
3737
},
38-
required: {
38+
nonNullWithDefault: {
3939
overriding: {
4040
kind: 'checked',
4141
type: t.boolean,

src/db/models/location.ts

+37
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { defineIDModel } from '../util/id-model';
56

67
export type LocationId = Brand<
78
number,
@@ -10,3 +11,39 @@ export type LocationId = Brand<
1011
>;
1112

1213
export const LOCATION_ID = brandedType<number, LocationId>(t.number);
14+
15+
const LOCATION_STATUS = t.keyof({
16+
active: null,
17+
expired: null,
18+
});
19+
20+
export default defineIDModel({
21+
tableName: 'location',
22+
fields: {
23+
generated: {
24+
id: { kind: 'branded-integer', brand: LOCATION_ID },
25+
},
26+
optional: {
27+
externalId: { kind: 'checked', type: t.string },
28+
name: { kind: 'checked', type: t.string },
29+
latitude: { kind: 'checked', type: t.number },
30+
longitude: { kind: 'checked', type: t.number },
31+
iso3: { kind: 'checked', type: t.string },
32+
pcode: { kind: 'checked', type: t.string },
33+
// Even though this column is defined as int8, it is
34+
// fetched as a string by knex, since it is bigint
35+
validOn: { kind: 'checked', type: t.string },
36+
parentId: { kind: 'branded-integer', brand: LOCATION_ID },
37+
},
38+
accidentallyOptional: {
39+
adminLevel: { kind: 'checked', type: t.number },
40+
status: {
41+
kind: 'checked',
42+
type: LOCATION_STATUS,
43+
},
44+
itosSync: { kind: 'checked', type: t.boolean },
45+
},
46+
},
47+
idField: 'id',
48+
softDeletionEnabled: false,
49+
});

src/db/models/planEntityVersion.ts

+2-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { LOCALIZED_PLURAL_STRING } from '../util/datatypes';
56
import { defineLegacyVersionedModel } from '../util/legacy-versioned-model';
67
import { ENTITY_PROTOTYPE_ID } from './entityPrototype';
78
import { PLAN_ENTITY_ID } from './planEntity';
@@ -29,12 +30,7 @@ export const PLAN_ENTITY_VERSION_VALUE = t.type({
2930
}),
3031
])
3132
),
32-
type: t.type({
33-
en: t.type({
34-
singular: t.string,
35-
plural: t.string,
36-
}),
37-
}),
33+
type: LOCALIZED_PLURAL_STRING,
3834
});
3935
export type PlanEntityVersionValue = t.TypeOf<typeof PLAN_ENTITY_VERSION_VALUE>;
4036

src/db/models/planVersion.ts

+44
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { DATE } from '../util/datatypes';
6+
import { defineLegacyVersionedModel } from '../util/legacy-versioned-model';
7+
import { PLAN_ID } from './plan';
8+
import { PLAN_REPORTING_PERIOD_ID } from './planReportingPeriod';
59

610
export type PlanVersionId = Brand<
711
number,
@@ -10,3 +14,43 @@ export type PlanVersionId = Brand<
1014
>;
1115

1216
export const PLAN_VERSION_ID = brandedType<number, PlanVersionId>(t.number);
17+
18+
const PLAN_VERSION_CLUSTER_SELECTION_TYPE = t.keyof({
19+
single: null,
20+
multi: null,
21+
});
22+
23+
export default defineLegacyVersionedModel({
24+
tableName: 'planVersion',
25+
fields: {
26+
generated: {
27+
id: { kind: 'branded-integer', brand: PLAN_VERSION_ID },
28+
},
29+
nonNullWithDefault: {
30+
isForHPCProjects: { kind: 'checked', type: t.boolean },
31+
},
32+
accidentallyOptional: {
33+
planId: { kind: 'branded-integer', brand: PLAN_ID },
34+
name: { kind: 'checked', type: t.string },
35+
startDate: { kind: 'checked', type: DATE },
36+
endDate: { kind: 'checked', type: DATE },
37+
},
38+
optional: {
39+
comments: { kind: 'checked', type: t.string },
40+
code: { kind: 'checked', type: t.string },
41+
customLocationCode: { kind: 'checked', type: t.string },
42+
currentReportingPeriodId: {
43+
kind: 'branded-integer',
44+
brand: PLAN_REPORTING_PERIOD_ID,
45+
},
46+
lastPublishedReportingPeriodId: { kind: 'checked', type: t.number },
47+
// Even though this column isn't defined using DB enum only two values are used
48+
clusterSelectionType: {
49+
kind: 'checked',
50+
type: PLAN_VERSION_CLUSTER_SELECTION_TYPE,
51+
},
52+
},
53+
},
54+
idField: 'id',
55+
softDeletionEnabled: false,
56+
});

src/db/models/planYear.ts

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { defineLegacyVersionedModel } from '../util/legacy-versioned-model';
6+
import { PLAN_ID } from './plan';
7+
import { USAGE_YEAR_ID } from './usageYear';
58

69
export type PlanYearId = Brand<
710
number,
@@ -10,3 +13,18 @@ export type PlanYearId = Brand<
1013
>;
1114

1215
export const PLAN_YEAR_ID = brandedType<number, PlanYearId>(t.number);
16+
17+
export default defineLegacyVersionedModel({
18+
tableName: 'planYear',
19+
fields: {
20+
generated: {
21+
id: { kind: 'branded-integer', brand: PLAN_YEAR_ID },
22+
},
23+
required: {
24+
planId: { kind: 'branded-integer', brand: PLAN_ID },
25+
usageYearId: { kind: 'branded-integer', brand: USAGE_YEAR_ID },
26+
},
27+
},
28+
idField: 'id',
29+
softDeletionEnabled: true,
30+
});

src/db/models/project.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ export const PROJECT_VERSION_ID = brandedType<number, ProjectVersionId>(
3030
t.number
3131
);
3232

33+
const PROJECT_IMPLEMENTATION_STATUS = {
34+
Planning: null,
35+
Implementing: null,
36+
'Ended - Completed': null,
37+
'Ended - Terminated': null,
38+
'Ended - Not started and abandoned': null,
39+
};
40+
3341
const PROJECT_PDF_ENTRY = t.type({
3442
/**
3543
* TODO: use something more stable, like UNIX OFFSET as a number
@@ -60,7 +68,10 @@ export default defineIDModel({
6068
},
6169
optional: {
6270
code: { kind: 'checked', type: t.string },
63-
implementationStatus: { kind: 'checked', type: t.string },
71+
implementationStatus: {
72+
kind: 'enum',
73+
values: PROJECT_IMPLEMENTATION_STATUS,
74+
},
6475
currentPublishedVersionId: {
6576
kind: 'branded-integer',
6677
brand: PROJECT_VERSION_ID,

src/db/models/projectVersionAttachment.ts

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export default defineSequelizeModel({
1313
kind: 'branded-integer',
1414
brand: ATTACHMENT_VERSION_ID,
1515
},
16+
},
17+
optional: {
1618
value: { kind: 'checked', type: t.unknown },
1719
total: { kind: 'checked', type: t.number },
1820
},

src/db/models/usageYear.ts

+15
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { defineIDModel } from '../util/id-model';
56

67
export type UsageYearId = Brand<
78
number,
@@ -10,3 +11,17 @@ export type UsageYearId = Brand<
1011
>;
1112

1213
export const USAGE_YEAR_ID = brandedType<number, UsageYearId>(t.number);
14+
15+
export default defineIDModel({
16+
tableName: 'usageYear',
17+
fields: {
18+
generated: {
19+
id: { kind: 'branded-integer', brand: USAGE_YEAR_ID },
20+
},
21+
required: {
22+
year: { kind: 'checked', type: t.string },
23+
},
24+
},
25+
idField: 'id',
26+
softDeletionEnabled: false,
27+
});

src/db/models/workflowStatusOption.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as t from 'io-ts';
22

33
import { brandedType } from '../../util/io-ts';
44
import type { Brand } from '../../util/types';
5+
import { LOCALIZED_STRING } from '../util/datatypes';
56
import { defineIDModel } from '../util/id-model';
67
import { PLAN_ID } from './plan';
78

@@ -33,9 +34,7 @@ export const WORKFLOW_STATUS_OPTION_TYPE = t.keyof({
3334
});
3435

3536
export const WORKFLOW_STATUS_OPTION_VALUE = t.type({
36-
label: t.type({
37-
en: t.string,
38-
}),
37+
label: LOCALIZED_STRING,
3938
});
4039

4140
export default defineIDModel({

src/db/util/datatypes.ts

+14
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,17 @@ export const FILE_REFERENCE = t.type({
4949
});
5050

5151
export type FileReference = t.TypeOf<typeof FILE_REFERENCE>;
52+
53+
const localized = <T extends t.Type<any>>(type: T) =>
54+
t.type({
55+
en: type,
56+
});
57+
58+
export const LOCALIZED_STRING = localized(t.string);
59+
60+
export const LOCALIZED_PLURAL_STRING = localized(
61+
t.type({
62+
singular: t.string,
63+
plural: t.string,
64+
})
65+
);

src/util/async.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ export const createGroupableAsyncFunction = <
4444
}
4545
} catch (err) {
4646
for (const call of cs) {
47-
call.reject(err);
47+
if (err instanceof Error) {
48+
call.reject(err);
49+
}
4850
}
4951
}
5052
};

0 commit comments

Comments
 (0)