@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.
@@ -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 setState = (authUser, user) => {
31
- Object.assign(state, { authUser, user });
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
- setState(null, null);
35
+ Object.assign(state, { authUser: null, user: null });
38
36
  return fn(state);
39
37
  }
40
- createClient(coreApiUrl, authUser)(`/users/${authUser.uid}`)
38
+ Object.assign(state, { authUser });
39
+ coreAPI.user.findById(authUser.uid)
41
40
  .then((user) => {
42
- Object.assign(user, {
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
- setState(null, null)
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
- ...createJobsAPI(jobsApiUrl, state),
68
- ...createTeamAPI(teamApiUrl, state),
69
- ...createCoreAPI(coreApiUrl, state),
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
- 'locale',
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
- 'project',
75
- 'stage',
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 = Array.isArray(attrSchema.type) ? attrSchema.type[0] : attrSchema.type;
10
- const isRequired = !Array.isArray(attrSchema.type) || !attrSchema.type.includes('null');
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 createModel = (baseUrl, state, collectionName, schema = {}) => {
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.id), {
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.id
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.1.1",
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
- "firebase": "^9.5.0"
10
+ "blueimp-md5": "^2.19.0",
11
+ "firebase": "^9.6.2"
11
12
  },
12
13
  "devDependencies": {
13
- "@babel/core": "^7.16.0",
14
- "@babel/plugin-transform-modules-commonjs": "^7.16.0",
15
- "babel-jest": "^27.4.2",
16
- "jest": "^27.4.3",
17
- "webpack": "^5.64.4",
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
- "signupUser": {
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": "string"
382
+ "type": [
383
+ "string",
384
+ "null"
385
+ ]
353
386
  },
354
- "project": {
355
- "type": "string"
387
+ "lastProject": {
388
+ "type": [
389
+ "string",
390
+ "null"
391
+ ]
356
392
  },
357
- "stage": {
393
+ "completedProjects": {
358
394
  "type": "integer"
359
395
  },
360
396
  "notes": {