@laboratoria/sdk-js 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/node.js.yml +31 -0
- package/index.js +11 -18
- package/lib/core.js +34 -6
- package/lib/model.js +52 -10
- package/lib/model.spec.js +29 -1
- package/package.json +9 -8
- package/schemas/core.json +43 -7
@@ -0,0 +1,31 @@
|
|
1
|
+
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
2
|
+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
3
|
+
|
4
|
+
name: Node.js CI
|
5
|
+
|
6
|
+
on:
|
7
|
+
push:
|
8
|
+
branches: [ main ]
|
9
|
+
pull_request:
|
10
|
+
branches: [ main ]
|
11
|
+
|
12
|
+
jobs:
|
13
|
+
build:
|
14
|
+
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
strategy:
|
18
|
+
matrix:
|
19
|
+
node-version: [14.x, 16.x]
|
20
|
+
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
21
|
+
|
22
|
+
steps:
|
23
|
+
- uses: actions/checkout@v2
|
24
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
25
|
+
uses: actions/setup-node@v2
|
26
|
+
with:
|
27
|
+
node-version: ${{ matrix.node-version }}
|
28
|
+
cache: 'npm'
|
29
|
+
- run: npm ci
|
30
|
+
- run: npm run build --if-present
|
31
|
+
- run: npm test
|
package/index.js
CHANGED
@@ -5,7 +5,6 @@ import {
|
|
5
5
|
signInWithEmailAndPassword,
|
6
6
|
signOut,
|
7
7
|
} from 'firebase/auth';
|
8
|
-
import { createClient } from './lib/client';
|
9
8
|
import { createAPI as createCoreAPI } from './lib/core';
|
10
9
|
import { createAPI as createJobsAPI } from './lib/jobs';
|
11
10
|
import { createAPI as createTeamAPI } from './lib/team';
|
@@ -26,31 +25,25 @@ export const createApp = ({
|
|
26
25
|
|
27
26
|
const firebaseAuth = getAuth(firebaseApp);
|
28
27
|
const state = { authUser: undefined, user: undefined };
|
29
|
-
|
30
|
-
const
|
31
|
-
|
32
|
-
};
|
28
|
+
const coreAPI = createCoreAPI(coreApiUrl, state);
|
29
|
+
const teamAPI = createTeamAPI(teamApiUrl, state);
|
30
|
+
const jobsAPI = createJobsAPI(jobsApiUrl, state);
|
33
31
|
|
34
32
|
const authAPI = {
|
35
33
|
onChange: fn => onAuthStateChanged(firebaseAuth, (authUser) => {
|
36
34
|
if (!authUser) {
|
37
|
-
|
35
|
+
Object.assign(state, { authUser: null, user: null });
|
38
36
|
return fn(state);
|
39
37
|
}
|
40
|
-
|
38
|
+
Object.assign(state, { authUser });
|
39
|
+
coreAPI.user.findById(authUser.uid)
|
41
40
|
.then((user) => {
|
42
|
-
Object.assign(
|
43
|
-
isStaff: ['staff', 'manager', 'finance', 'admin'].includes(user.role),
|
44
|
-
isManager: ['manager', 'finance', 'admin'].includes(user.role),
|
45
|
-
isFinance: ['finance', 'admin'].includes(user.role),
|
46
|
-
isAdmin: user.role === 'admin',
|
47
|
-
});
|
48
|
-
setState(authUser, user);
|
41
|
+
Object.assign(state, { user });
|
49
42
|
fn(state);
|
50
43
|
})
|
51
44
|
.catch((err) => {
|
52
45
|
console.error(err);
|
53
|
-
|
46
|
+
Object.assign(state, { authUser: null, user: null });
|
54
47
|
fn(state);
|
55
48
|
});
|
56
49
|
}),
|
@@ -64,8 +57,8 @@ export const createApp = ({
|
|
64
57
|
|
65
58
|
return {
|
66
59
|
auth: authAPI,
|
67
|
-
...
|
68
|
-
...
|
69
|
-
...
|
60
|
+
...jobsAPI,
|
61
|
+
...teamAPI,
|
62
|
+
...coreAPI,
|
70
63
|
};
|
71
64
|
};
|
package/lib/core.js
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import md5 from 'blueimp-md5';
|
1
2
|
import { createModels, extendSchemaDefinitions } from './model';
|
2
3
|
import schema from '../schemas/core.json';
|
3
4
|
|
@@ -8,7 +9,7 @@ const extended = {
|
|
8
9
|
'firstName',
|
9
10
|
'lastName',
|
10
11
|
'email',
|
11
|
-
'
|
12
|
+
'lang',
|
12
13
|
'github',
|
13
14
|
'linkedin',
|
14
15
|
'bio',
|
@@ -22,17 +23,24 @@ const extended = {
|
|
22
23
|
email: {
|
23
24
|
inputType: 'email',
|
24
25
|
},
|
25
|
-
locale: {
|
26
|
-
enum: ['es-ES', 'pt-BR'],
|
27
|
-
},
|
28
26
|
bio: {
|
29
27
|
inputType: 'textarea',
|
30
28
|
},
|
31
29
|
},
|
32
30
|
parse: (props) => {
|
31
|
+
const avatar = (
|
32
|
+
props.github
|
33
|
+
? `https://github.com/${props.github}.png?size=`
|
34
|
+
: `https://www.gravatar.com/avatar/${md5(props.email)}?s=`
|
35
|
+
)
|
33
36
|
return {
|
34
37
|
...props,
|
35
38
|
fullName: `${props.firstName} ${props.lastName}`,
|
39
|
+
avatar: size => `${avatar}${size}`,
|
40
|
+
isStaff: ['staff', 'manager', 'finance', 'admin'].includes(props.role),
|
41
|
+
isManager: ['manager', 'finance', 'admin'].includes(props.role),
|
42
|
+
isFinance: ['finance', 'admin'].includes(props.role),
|
43
|
+
isAdmin: props.role === 'admin',
|
36
44
|
};
|
37
45
|
},
|
38
46
|
},
|
@@ -59,6 +67,26 @@ const extended = {
|
|
59
67
|
`${name}${!stage ? '' : ` - ${stage.name}`} (id: ${id})`
|
60
68
|
),
|
61
69
|
properties: {},
|
70
|
+
parse: (props) => {
|
71
|
+
const now = Date.now();
|
72
|
+
const status = (
|
73
|
+
props.start > now
|
74
|
+
? 'upcoming'
|
75
|
+
: props.end < now
|
76
|
+
? 'past'
|
77
|
+
: 'active'
|
78
|
+
);
|
79
|
+
const size = (
|
80
|
+
status === 'upcoming' && props.vacancies
|
81
|
+
? props.vacancies
|
82
|
+
: props._count?.students || props.students?.length || 0
|
83
|
+
);
|
84
|
+
return {
|
85
|
+
...props,
|
86
|
+
status,
|
87
|
+
size,
|
88
|
+
};
|
89
|
+
},
|
62
90
|
},
|
63
91
|
Student: {
|
64
92
|
inputProps: [],
|
@@ -71,8 +99,8 @@ const extended = {
|
|
71
99
|
'date',
|
72
100
|
'reason',
|
73
101
|
'reasonDetail',
|
74
|
-
'
|
75
|
-
'
|
102
|
+
'lastProject',
|
103
|
+
'completedProjects',
|
76
104
|
'notes',
|
77
105
|
'staffSad',
|
78
106
|
'covidRelated',
|
package/lib/model.js
CHANGED
@@ -1,13 +1,21 @@
|
|
1
1
|
import { createClient } from './client.js';
|
2
2
|
|
3
|
+
|
3
4
|
const createValidator = (schema) => {
|
4
5
|
const properties = schema.properties || {};
|
5
6
|
const inputProps = schema.inputProps || Object.keys(properties);
|
6
7
|
|
7
8
|
const validateAttr = (key, value) => {
|
8
9
|
const attrSchema = properties[key] || {};
|
9
|
-
const type =
|
10
|
-
|
10
|
+
const type = (
|
11
|
+
Array.isArray(attrSchema.type)
|
12
|
+
? attrSchema.type[0]
|
13
|
+
: attrSchema.type
|
14
|
+
);
|
15
|
+
const isRequired = (
|
16
|
+
!Array.isArray(attrSchema.type)
|
17
|
+
|| !attrSchema.type.includes('null')
|
18
|
+
);
|
11
19
|
|
12
20
|
if (attrSchema.enum) {
|
13
21
|
if (!isRequired && !value) {
|
@@ -85,7 +93,44 @@ const buildDefaultInclude = schema => Object.keys(schema.properties || {}).reduc
|
|
85
93
|
);
|
86
94
|
|
87
95
|
|
88
|
-
const
|
96
|
+
const serializeData = (data, schema) => {
|
97
|
+
const hasInputProps = Array.isArray(schema.inputProps);
|
98
|
+
const payload = Object.keys(data).reduce(
|
99
|
+
(memo, key) => {
|
100
|
+
if (hasInputProps && !schema.inputProps.includes(key)) {
|
101
|
+
return memo;
|
102
|
+
}
|
103
|
+
const prop = (schema.properties || {})[key];
|
104
|
+
const isOptionalRelation = (
|
105
|
+
prop
|
106
|
+
&& Array.isArray(prop.anyOf)
|
107
|
+
&& prop.anyOf[0].$ref
|
108
|
+
&& prop.anyOf[1]
|
109
|
+
&& prop.anyOf[1].type === 'null'
|
110
|
+
);
|
111
|
+
if (isOptionalRelation && data[key] === null) {
|
112
|
+
return memo;
|
113
|
+
}
|
114
|
+
return {
|
115
|
+
...memo,
|
116
|
+
[key]: data[key],
|
117
|
+
};
|
118
|
+
},
|
119
|
+
{},
|
120
|
+
);
|
121
|
+
|
122
|
+
// {
|
123
|
+
// signupCohort: {
|
124
|
+
// connect: { id: signupCohort.id },
|
125
|
+
// },
|
126
|
+
// }
|
127
|
+
|
128
|
+
return payload;
|
129
|
+
};
|
130
|
+
|
131
|
+
|
132
|
+
export const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
133
|
+
const primaryKey = schema.primaryKey || 'id';
|
89
134
|
const validator = createValidator(schema);
|
90
135
|
const buildURL = createBuildURL(collectionName);
|
91
136
|
const defaultInclude = buildDefaultInclude(schema);
|
@@ -93,12 +138,12 @@ const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
|
93
138
|
const req = (...args) => createClient(baseUrl, state.authUser)(...args);
|
94
139
|
const create = q => req(buildURL(), {
|
95
140
|
method: 'POST',
|
96
|
-
body: buildQuery(q),
|
141
|
+
body: buildQuery(Object.assign(q, { data: serializeData(q.data, schema) })),
|
97
142
|
});
|
98
143
|
|
99
|
-
const put = q => req(buildURL(q.where
|
144
|
+
const put = q => req(buildURL(q.where[primaryKey]), {
|
100
145
|
method: 'PUT',
|
101
|
-
body: buildQuery(q),
|
146
|
+
body: buildQuery(Object.assign(q, { data: serializeData(q.data, schema) })),
|
102
147
|
});
|
103
148
|
|
104
149
|
const parse = data => {
|
@@ -139,7 +184,7 @@ const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
|
139
184
|
create,
|
140
185
|
update: put,
|
141
186
|
upsert: opts => (
|
142
|
-
!opts.where
|
187
|
+
!opts.where[primaryKey]
|
143
188
|
? create({ data: opts.create })
|
144
189
|
: put({ where: opts.where, data: opts.update })
|
145
190
|
),
|
@@ -147,9 +192,6 @@ const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
|
147
192
|
};
|
148
193
|
};
|
149
194
|
|
150
|
-
|
151
|
-
export default createModel;
|
152
|
-
|
153
195
|
export const createModels = (url, state, schema) => {
|
154
196
|
return Object.keys(schema.properties).reduce(
|
155
197
|
(prev, key) => {
|
package/lib/model.spec.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import createModel from './model.js';
|
1
|
+
import { createModel } from './model.js';
|
2
2
|
import { createClient } from './client.js';
|
3
3
|
|
4
4
|
jest.mock('./client.js');
|
@@ -78,6 +78,34 @@ describe('model.update(options)', () => {
|
|
78
78
|
method: 'PUT',
|
79
79
|
});
|
80
80
|
});
|
81
|
+
|
82
|
+
it('should exclude null values for optional relation props', async () => {
|
83
|
+
const client = createClient().mockResolvedValue({});
|
84
|
+
const schema = {
|
85
|
+
properties: {
|
86
|
+
signupCohort: {
|
87
|
+
anyOf: [
|
88
|
+
{ '$ref': '#/definitions/Cohort' },
|
89
|
+
{ type: 'null' },
|
90
|
+
],
|
91
|
+
},
|
92
|
+
},
|
93
|
+
};
|
94
|
+
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
95
|
+
expect(await model.update({
|
96
|
+
where: { id: 1 },
|
97
|
+
data: { signupCohort: null },
|
98
|
+
})).toEqual({});
|
99
|
+
expect(client).toHaveBeenCalledTimes(1);
|
100
|
+
expect(client).toHaveBeenCalledWith('/foo/1', {
|
101
|
+
body: {
|
102
|
+
where: { id: 1 },
|
103
|
+
data: {},
|
104
|
+
include: { signupCohort: true },
|
105
|
+
},
|
106
|
+
method: 'PUT',
|
107
|
+
});
|
108
|
+
});
|
81
109
|
});
|
82
110
|
|
83
111
|
describe('model.upsert(options)', () => {
|
package/package.json
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
{
|
2
2
|
"name": "@laboratoria/sdk-js",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.2.0",
|
4
4
|
"description": "Laboratoria JavaScript (browser) SDK",
|
5
5
|
"scripts": {
|
6
6
|
"test": "jest --verbose --coverage"
|
7
7
|
},
|
8
8
|
"license": "MIT",
|
9
9
|
"dependencies": {
|
10
|
-
"
|
10
|
+
"blueimp-md5": "^2.19.0",
|
11
|
+
"firebase": "^9.6.2"
|
11
12
|
},
|
12
13
|
"devDependencies": {
|
13
|
-
"@babel/core": "^7.16.
|
14
|
-
"@babel/plugin-transform-modules-commonjs": "^7.16.
|
15
|
-
"babel-jest": "^27.4.
|
16
|
-
"jest": "^27.4.
|
17
|
-
"webpack": "^5.
|
14
|
+
"@babel/core": "^7.16.7",
|
15
|
+
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
|
16
|
+
"babel-jest": "^27.4.6",
|
17
|
+
"jest": "^27.4.7",
|
18
|
+
"webpack": "^5.65.0",
|
18
19
|
"webpack-cli": "^4.9.1"
|
19
20
|
},
|
20
21
|
"jest": {
|
21
22
|
"testEnvironment": "jsdom"
|
22
23
|
}
|
23
|
-
}
|
24
|
+
}
|
package/schemas/core.json
CHANGED
@@ -75,7 +75,8 @@
|
|
75
75
|
]
|
76
76
|
},
|
77
77
|
"disabled": {
|
78
|
-
"type": "boolean"
|
78
|
+
"type": "boolean",
|
79
|
+
"default": false
|
79
80
|
},
|
80
81
|
"students": {
|
81
82
|
"type": "array",
|
@@ -228,6 +229,29 @@
|
|
228
229
|
"type": "string",
|
229
230
|
"format": "date-time"
|
230
231
|
},
|
232
|
+
"vacancies": {
|
233
|
+
"type": [
|
234
|
+
"integer",
|
235
|
+
"null"
|
236
|
+
]
|
237
|
+
},
|
238
|
+
"publicAdmission": {
|
239
|
+
"type": "boolean",
|
240
|
+
"default": false
|
241
|
+
},
|
242
|
+
"placementStart": {
|
243
|
+
"type": [
|
244
|
+
"string",
|
245
|
+
"null"
|
246
|
+
],
|
247
|
+
"format": "date-time"
|
248
|
+
},
|
249
|
+
"placementDuration": {
|
250
|
+
"type": [
|
251
|
+
"integer",
|
252
|
+
"null"
|
253
|
+
]
|
254
|
+
},
|
231
255
|
"program": {
|
232
256
|
"$ref": "#/definitions/Program"
|
233
257
|
},
|
@@ -255,7 +279,7 @@
|
|
255
279
|
"legacySlug": {
|
256
280
|
"type": "string"
|
257
281
|
},
|
258
|
-
"
|
282
|
+
"signupUsers": {
|
259
283
|
"type": "array",
|
260
284
|
"items": {
|
261
285
|
"$ref": "#/definitions/User"
|
@@ -346,15 +370,27 @@
|
|
346
370
|
"format": "date-time"
|
347
371
|
},
|
348
372
|
"reason": {
|
349
|
-
"type": "string"
|
373
|
+
"type": "string",
|
374
|
+
"enum": [
|
375
|
+
"dropout",
|
376
|
+
"noShow",
|
377
|
+
"invitedToLeave",
|
378
|
+
"changeCohort"
|
379
|
+
]
|
350
380
|
},
|
351
381
|
"reasonDetail": {
|
352
|
-
"type":
|
382
|
+
"type": [
|
383
|
+
"string",
|
384
|
+
"null"
|
385
|
+
]
|
353
386
|
},
|
354
|
-
"
|
355
|
-
"type":
|
387
|
+
"lastProject": {
|
388
|
+
"type": [
|
389
|
+
"string",
|
390
|
+
"null"
|
391
|
+
]
|
356
392
|
},
|
357
|
-
"
|
393
|
+
"completedProjects": {
|
358
394
|
"type": "integer"
|
359
395
|
},
|
360
396
|
"notes": {
|