@laboratoria/sdk-js 0.2.0 → 1.3.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/README.md +40 -3
- package/index.js +4 -5
- package/lib/core.js +30 -5
- package/lib/curriculum.js +59 -0
- package/lib/model.js +78 -54
- package/package.json +18 -9
- package/schemas/core.json +150 -4
- package/.github/workflows/node.js.yml +0 -31
- package/index.spec.js +0 -112
- package/lib/client.spec.js +0 -77
- package/lib/currencies.js +0 -3
- package/lib/model.spec.js +0 -243
- package/lib/team.js +0 -52
- package/schemas/team.json +0 -146
package/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
## Laboratoria JavaScript (browser) SDK
|
2
2
|
|
3
|
+
[](https://github.com/Laboratoria/sdk-js/actions/workflows/node.js.yml)
|
4
|
+
|
3
5
|
:warning: This tool is still in draft stage and is likely to change without
|
4
6
|
notice.
|
5
7
|
|
@@ -25,7 +27,6 @@ const app = createApp({
|
|
25
27
|
firebaseApiKey: '',
|
26
28
|
firebaseProject: '',
|
27
29
|
coreApiUrl: '',
|
28
|
-
teamApiUrl: '',
|
29
30
|
jobsApiUrl: '',
|
30
31
|
});
|
31
32
|
```
|
@@ -79,7 +80,6 @@ app.user.create({
|
|
79
80
|
### Cohorts
|
80
81
|
|
81
82
|
```js
|
82
|
-
// Cohorts API
|
83
83
|
app.cohort.findMany({
|
84
84
|
where: {
|
85
85
|
end: { gt: new Date() },
|
@@ -96,13 +96,50 @@ app.cohort.findMany({
|
|
96
96
|
|
97
97
|
### Students
|
98
98
|
|
99
|
+
WIP
|
100
|
+
|
99
101
|
### Dropouts
|
100
102
|
|
103
|
+
WIP
|
101
104
|
|
102
105
|
### Projects
|
103
106
|
|
104
107
|
```js
|
105
108
|
app.project.findMany();
|
106
109
|
|
107
|
-
|
110
|
+
app.project.findMany({ tag: 'v3.0.0' });
|
111
|
+
|
112
|
+
app.project.findMany({
|
113
|
+
locale: 'es-ES',
|
114
|
+
track: 'js',
|
115
|
+
});
|
116
|
+
|
117
|
+
app.project.findById('cipher');
|
118
|
+
|
119
|
+
app.project.findById('cipher', { tag: 'v3.0.0' });
|
108
120
|
```
|
121
|
+
|
122
|
+
### Topics
|
123
|
+
|
124
|
+
```js
|
125
|
+
app.topic.findMany();
|
126
|
+
|
127
|
+
app.topic.findMany({ tag: 'v3.0.0' });
|
128
|
+
|
129
|
+
app.topic.findMany({
|
130
|
+
locale: 'es-ES',
|
131
|
+
track: 'js',
|
132
|
+
});
|
133
|
+
|
134
|
+
app.topic.findById('javascript');
|
135
|
+
|
136
|
+
app.topic.findById('javascript', { tag: 'v3.0.0' });
|
137
|
+
```
|
138
|
+
|
139
|
+
### Learning Objectives
|
140
|
+
|
141
|
+
```js
|
142
|
+
app.learningObjective.findMany();
|
143
|
+
|
144
|
+
app.learningObjective.findMany({ tag: 'v3.0.0' });
|
145
|
+
```
|
package/index.js
CHANGED
@@ -6,14 +6,13 @@ import {
|
|
6
6
|
signOut,
|
7
7
|
} from 'firebase/auth';
|
8
8
|
import { createAPI as createCoreAPI } from './lib/core';
|
9
|
+
import { createAPI as createCurriculumAPI } from './lib/curriculum';
|
9
10
|
import { createAPI as createJobsAPI } from './lib/jobs';
|
10
|
-
import { createAPI as createTeamAPI } from './lib/team';
|
11
11
|
|
12
12
|
export const createApp = ({
|
13
13
|
firebaseApiKey = 'AIzaSyAXbaEbpq8NOfn0r8mIrcoHvoGRkJThwdc',
|
14
14
|
firebaseProject = 'laboratoria-la',
|
15
15
|
coreApiUrl = 'https://us-central1-outpost-272720.cloudfunctions.net/core-api',
|
16
|
-
teamApiUrl = 'https://us-central1-outpost-272720.cloudfunctions.net/team-api',
|
17
16
|
jobsApiUrl = 'https://us-central1-outpost-272720.cloudfunctions.net/jobs-api',
|
18
17
|
} = {}) => {
|
19
18
|
const firebaseApp = initializeApp({
|
@@ -26,8 +25,8 @@ export const createApp = ({
|
|
26
25
|
const firebaseAuth = getAuth(firebaseApp);
|
27
26
|
const state = { authUser: undefined, user: undefined };
|
28
27
|
const coreAPI = createCoreAPI(coreApiUrl, state);
|
29
|
-
const teamAPI = createTeamAPI(teamApiUrl, state);
|
30
28
|
const jobsAPI = createJobsAPI(jobsApiUrl, state);
|
29
|
+
const curriculumAPI = createCurriculumAPI();
|
31
30
|
|
32
31
|
const authAPI = {
|
33
32
|
onChange: fn => onAuthStateChanged(firebaseAuth, (authUser) => {
|
@@ -56,9 +55,9 @@ export const createApp = ({
|
|
56
55
|
};
|
57
56
|
|
58
57
|
return {
|
59
|
-
auth: authAPI,
|
60
58
|
...jobsAPI,
|
61
|
-
...
|
59
|
+
...curriculumAPI,
|
62
60
|
...coreAPI,
|
61
|
+
auth: authAPI,
|
63
62
|
};
|
64
63
|
};
|
package/lib/core.js
CHANGED
@@ -3,6 +3,11 @@ import { createModels, extendSchemaDefinitions } from './model';
|
|
3
3
|
import schema from '../schemas/core.json';
|
4
4
|
|
5
5
|
const extended = {
|
6
|
+
Country: {
|
7
|
+
primaryKey: 'code',
|
8
|
+
plural: 'countries',
|
9
|
+
searchProps: ['code', 'name'],
|
10
|
+
},
|
6
11
|
User: {
|
7
12
|
primaryKey: 'uid',
|
8
13
|
inputProps: [
|
@@ -69,6 +74,7 @@ const extended = {
|
|
69
74
|
properties: {},
|
70
75
|
parse: (props) => {
|
71
76
|
const now = Date.now();
|
77
|
+
// TODO: Handle case where start and/or end have not been selected in query
|
72
78
|
const status = (
|
73
79
|
props.start > now
|
74
80
|
? 'upcoming'
|
@@ -113,14 +119,33 @@ const extended = {
|
|
113
119
|
'cohort.name',
|
114
120
|
],
|
115
121
|
},
|
122
|
+
Contract: {
|
123
|
+
inputProps: [
|
124
|
+
'user',
|
125
|
+
'country',
|
126
|
+
'isEmployment',
|
127
|
+
'feeAmount',
|
128
|
+
'hoursPerWeek',
|
129
|
+
'start',
|
130
|
+
'end',
|
131
|
+
],
|
132
|
+
searchProps: ['user.firstName'],
|
133
|
+
getOptionLabel: ({ id, user }) => `${user?.firstName} (id: ${id})`,
|
134
|
+
},
|
135
|
+
Gig: {
|
136
|
+
inputProps: [
|
137
|
+
'cohort',
|
138
|
+
'user',
|
139
|
+
'contract',
|
140
|
+
'role',
|
141
|
+
'hoursPerWeek',
|
142
|
+
'start',
|
143
|
+
'end',
|
144
|
+
],
|
145
|
+
},
|
116
146
|
};
|
117
147
|
|
118
148
|
export const createAPI = (url, state) => createModels(url, state, {
|
119
149
|
...schema,
|
120
150
|
definitions: extendSchemaDefinitions(schema, extended),
|
121
151
|
});
|
122
|
-
|
123
|
-
// const {
|
124
|
-
// delete: _,
|
125
|
-
// ...userAPI,
|
126
|
-
// } = createModel(coreApiUrl, state, 'users', schemas.user);
|
@@ -0,0 +1,59 @@
|
|
1
|
+
const releasesUrl = 'https://api.github.com/repos/Laboratoria/bootcamp/releases';
|
2
|
+
const rawUrl = 'https://raw.githubusercontent.com/Laboratoria/bootcamp';
|
3
|
+
|
4
|
+
const getLatestVersion = () => fetch(`${releasesUrl}/latest`)
|
5
|
+
.then(resp => resp.json())
|
6
|
+
.then(({ tag_name }) => tag_name);
|
7
|
+
|
8
|
+
const fetchProjects = async tag => (
|
9
|
+
fetch(`${rawUrl}/${tag || await getLatestVersion()}/dist/projects.json`)
|
10
|
+
.then(resp => resp.json())
|
11
|
+
);
|
12
|
+
|
13
|
+
const fetchProject = async (slug, tag) => (
|
14
|
+
fetch(`${rawUrl}/${tag || await getLatestVersion()}/dist/projects/${slug}.json`)
|
15
|
+
.then(resp => resp.json())
|
16
|
+
);
|
17
|
+
|
18
|
+
const fetchTopics = async tag => (
|
19
|
+
fetch(`${rawUrl}/${tag || await getLatestVersion()}/dist/topics.json`)
|
20
|
+
.then(resp => resp.json())
|
21
|
+
);
|
22
|
+
|
23
|
+
const fetchTopic = async (slug, tag) => (
|
24
|
+
fetch(`${rawUrl}/${tag || await getLatestVersion()}/dist/topics/${slug}.json`)
|
25
|
+
.then(resp => resp.json())
|
26
|
+
);
|
27
|
+
|
28
|
+
const fetchLearningObjectives = async tag => (
|
29
|
+
fetch(`${rawUrl}/${tag || await getLatestVersion()}/dist/learning-objectives.json`)
|
30
|
+
.then(resp => resp.json())
|
31
|
+
);
|
32
|
+
|
33
|
+
const filterByLocaleAndTrack = opts => arr => arr.filter((obj) => {
|
34
|
+
if (opts.locale && opts.locale !== obj.locale) {
|
35
|
+
return false;
|
36
|
+
}
|
37
|
+
if (opts.track && opts.track !== obj.track) {
|
38
|
+
return false;
|
39
|
+
}
|
40
|
+
return true;
|
41
|
+
});
|
42
|
+
|
43
|
+
export const createAPI = () => {
|
44
|
+
return {
|
45
|
+
projects: {
|
46
|
+
findMany: (opts = {}) => fetchProjects(opts.tag)
|
47
|
+
.then(filterByLocaleAndTrack(opts)),
|
48
|
+
findById: (slug, opts = {}) => fetchProject(slug, opts.tag),
|
49
|
+
},
|
50
|
+
topics: {
|
51
|
+
findMany: (opts = {}) => fetchTopics(opts.tag)
|
52
|
+
.then(filterByLocaleAndTrack(opts)),
|
53
|
+
findById: (slug, opts = {}) => fetchTopic(slug, opts.tag),
|
54
|
+
},
|
55
|
+
learningObjectives: {
|
56
|
+
findMany: (opts = {}) => fetchLearningObjectives(opts.tag),
|
57
|
+
},
|
58
|
+
};
|
59
|
+
};
|
package/lib/model.js
CHANGED
@@ -1,6 +1,38 @@
|
|
1
1
|
import { createClient } from './client.js';
|
2
2
|
|
3
3
|
|
4
|
+
const isRequiredOneToOneRelation = (schema, key) => (
|
5
|
+
!!schema.properties
|
6
|
+
&& !!schema.properties[key]
|
7
|
+
&& !!schema.properties[key].$ref
|
8
|
+
);
|
9
|
+
|
10
|
+
const isOptionalOneToOneRelation = (schema, key) => (
|
11
|
+
!!schema.properties
|
12
|
+
&& !!schema.properties[key]
|
13
|
+
&& Array.isArray(schema.properties[key].anyOf)
|
14
|
+
&& !!schema.properties[key].anyOf[0]?.$ref
|
15
|
+
&& schema.properties[key].anyOf[1]?.type === 'null'
|
16
|
+
);
|
17
|
+
|
18
|
+
const isOneToOneRelation = (schema, key) => (
|
19
|
+
isRequiredOneToOneRelation(schema, key)
|
20
|
+
|| isOptionalOneToOneRelation(schema, key)
|
21
|
+
);
|
22
|
+
|
23
|
+
const isOneToManyRelation = (schema, key) => (
|
24
|
+
schema.properties
|
25
|
+
&& schema.properties[key]
|
26
|
+
&& schema.properties[key].type === 'array'
|
27
|
+
&& !!schema.properties[key].items.$ref
|
28
|
+
);
|
29
|
+
|
30
|
+
// const isRelation = (schema, key) => (
|
31
|
+
// isOneToOneRelation(schema, key)
|
32
|
+
// || isOneToManyRelation(schema, key)
|
33
|
+
// );
|
34
|
+
|
35
|
+
|
4
36
|
const createValidator = (schema) => {
|
5
37
|
const properties = schema.properties || {};
|
6
38
|
const inputProps = schema.inputProps || Object.keys(properties);
|
@@ -71,44 +103,14 @@ const createBuildURL = collectionName => (id, q) => {
|
|
71
103
|
};
|
72
104
|
|
73
105
|
|
74
|
-
const createBuildQuery = defaultInclude => (q = {}) => ({
|
75
|
-
...(!q.select && Object.keys(defaultInclude).length && { include: defaultInclude }),
|
76
|
-
...q,
|
77
|
-
});
|
78
|
-
|
79
|
-
|
80
|
-
const buildDefaultInclude = schema => Object.keys(schema.properties || {}).reduce(
|
81
|
-
(memo, key) => (
|
82
|
-
(
|
83
|
-
schema.properties[key].$ref
|
84
|
-
|| (
|
85
|
-
Array.isArray(schema.properties[key].anyOf)
|
86
|
-
&& schema.properties[key].anyOf[0].$ref
|
87
|
-
)
|
88
|
-
)
|
89
|
-
? { ...memo, [key]: true }
|
90
|
-
: memo
|
91
|
-
),
|
92
|
-
{},
|
93
|
-
);
|
94
|
-
|
95
|
-
|
96
106
|
const serializeData = (data, schema) => {
|
97
107
|
const hasInputProps = Array.isArray(schema.inputProps);
|
98
|
-
|
108
|
+
return Object.keys(data).reduce(
|
99
109
|
(memo, key) => {
|
100
110
|
if (hasInputProps && !schema.inputProps.includes(key)) {
|
101
111
|
return memo;
|
102
112
|
}
|
103
|
-
|
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) {
|
113
|
+
if (isOptionalOneToOneRelation(schema, key) && data[key] === null) {
|
112
114
|
return memo;
|
113
115
|
}
|
114
116
|
return {
|
@@ -118,14 +120,6 @@ const serializeData = (data, schema) => {
|
|
118
120
|
},
|
119
121
|
{},
|
120
122
|
);
|
121
|
-
|
122
|
-
// {
|
123
|
-
// signupCohort: {
|
124
|
-
// connect: { id: signupCohort.id },
|
125
|
-
// },
|
126
|
-
// }
|
127
|
-
|
128
|
-
return payload;
|
129
123
|
};
|
130
124
|
|
131
125
|
|
@@ -133,18 +127,18 @@ export const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
|
133
127
|
const primaryKey = schema.primaryKey || 'id';
|
134
128
|
const validator = createValidator(schema);
|
135
129
|
const buildURL = createBuildURL(collectionName);
|
136
|
-
const defaultInclude = buildDefaultInclude(schema);
|
137
|
-
const buildQuery = createBuildQuery(defaultInclude);
|
138
130
|
const req = (...args) => createClient(baseUrl, state.authUser)(...args);
|
139
|
-
const create = q => req(buildURL(), {
|
131
|
+
const create = ({ data, ...q }) => req(buildURL(null, q), {
|
140
132
|
method: 'POST',
|
141
|
-
body:
|
142
|
-
});
|
143
|
-
|
144
|
-
const put = q => req(buildURL(q.where[primaryKey]), {
|
145
|
-
method: 'PUT',
|
146
|
-
body: buildQuery(Object.assign(q, { data: serializeData(q.data, schema) })),
|
133
|
+
body: serializeData(data, schema),
|
147
134
|
});
|
135
|
+
const put = ({ data, ...q }) => {
|
136
|
+
const { where, ...rest } = q;
|
137
|
+
return req(buildURL(where[primaryKey], rest), {
|
138
|
+
method: 'PUT',
|
139
|
+
body: serializeData(data, schema),
|
140
|
+
});
|
141
|
+
};
|
148
142
|
|
149
143
|
const parse = data => {
|
150
144
|
const parsed = Object.keys(data).reduce(
|
@@ -168,18 +162,48 @@ export const createModel = (baseUrl, state, collectionName, schema = {}) => {
|
|
168
162
|
);
|
169
163
|
};
|
170
164
|
|
165
|
+
const relations = Object.keys(schema.properties || {}).reduce(
|
166
|
+
(memo, key) => (
|
167
|
+
isRequiredOneToOneRelation(schema, key)
|
168
|
+
? Object.assign(memo, {
|
169
|
+
all: memo.all.concat(key),
|
170
|
+
oneToOne: memo.oneToOne.concat(key),
|
171
|
+
requiredOneToOne: memo.requiredOneToOne.concat(key),
|
172
|
+
})
|
173
|
+
: isOptionalOneToOneRelation(schema, key)
|
174
|
+
? Object.assign(memo, {
|
175
|
+
all: memo.all.concat(key),
|
176
|
+
oneToOne: memo.oneToOne.concat(key),
|
177
|
+
optionalOneToOne: memo.optionalOneToOne.concat(key),
|
178
|
+
})
|
179
|
+
: isOneToManyRelation(schema, key)
|
180
|
+
? Object.assign(memo, {
|
181
|
+
all: memo.all.concat(key),
|
182
|
+
oneToMany: memo.oneToMany.concat(key),
|
183
|
+
})
|
184
|
+
: memo
|
185
|
+
),
|
186
|
+
{
|
187
|
+
all: [],
|
188
|
+
oneToOne: [],
|
189
|
+
requiredOneToOne: [],
|
190
|
+
optionalOneToOne: [],
|
191
|
+
oneToMany: [],
|
192
|
+
},
|
193
|
+
);
|
194
|
+
|
171
195
|
return {
|
172
196
|
get schema() {
|
173
|
-
return schema;
|
197
|
+
return { ...schema, primaryKey };
|
174
198
|
},
|
175
|
-
get
|
176
|
-
return
|
199
|
+
get relations() {
|
200
|
+
return relations;
|
177
201
|
},
|
178
202
|
validateAttr: validator.validateAttr,
|
179
203
|
validate: validator.validate,
|
180
|
-
findMany: q => req(buildURL(null,
|
204
|
+
findMany: q => req(buildURL(null, q))
|
181
205
|
.then(results => results.map(parse)),
|
182
|
-
findById: (id, q) => req(buildURL(id,
|
206
|
+
findById: (id, q) => req(buildURL(id, q))
|
183
207
|
.then(raw => !!raw ? parse(raw) : raw),
|
184
208
|
create,
|
185
209
|
update: put,
|
package/package.json
CHANGED
@@ -1,24 +1,33 @@
|
|
1
1
|
{
|
2
2
|
"name": "@laboratoria/sdk-js",
|
3
|
-
"version": "
|
3
|
+
"version": "1.3.0",
|
4
4
|
"description": "Laboratoria JavaScript (browser) SDK",
|
5
5
|
"scripts": {
|
6
|
-
"test": "jest --verbose --coverage"
|
6
|
+
"test": "jest --verbose --coverage",
|
7
|
+
"changelog": "git log $(git describe --tags --abbrev=0)..HEAD --oneline --format=\"* %h %s (%an)\""
|
7
8
|
},
|
8
9
|
"license": "MIT",
|
9
10
|
"dependencies": {
|
10
11
|
"blueimp-md5": "^2.19.0",
|
11
|
-
"firebase": "^9.6.
|
12
|
+
"firebase": "^9.6.6"
|
12
13
|
},
|
13
14
|
"devDependencies": {
|
14
|
-
"@babel/core": "^7.
|
15
|
+
"@babel/core": "^7.17.2",
|
15
16
|
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
|
16
|
-
"babel-jest": "^27.
|
17
|
-
"jest": "^27.
|
18
|
-
"webpack": "^5.
|
19
|
-
"webpack-cli": "^4.9.
|
17
|
+
"babel-jest": "^27.5.1",
|
18
|
+
"jest": "^27.5.1",
|
19
|
+
"webpack": "^5.68.0",
|
20
|
+
"webpack-cli": "^4.9.2"
|
20
21
|
},
|
21
22
|
"jest": {
|
22
|
-
"testEnvironment": "jsdom"
|
23
|
+
"testEnvironment": "jsdom",
|
24
|
+
"coverageThreshold": {
|
25
|
+
"global": {
|
26
|
+
"statements": 97,
|
27
|
+
"branches": 92,
|
28
|
+
"functions": 98,
|
29
|
+
"lines": 97
|
30
|
+
}
|
31
|
+
}
|
23
32
|
}
|
24
33
|
}
|
package/schemas/core.json
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
{
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
3
|
"definitions": {
|
4
|
+
"Country": {
|
5
|
+
"type": "object",
|
6
|
+
"properties": {
|
7
|
+
"code": {
|
8
|
+
"type": "string"
|
9
|
+
},
|
10
|
+
"name": {
|
11
|
+
"type": "string"
|
12
|
+
},
|
13
|
+
"contracts": {
|
14
|
+
"type": "array",
|
15
|
+
"items": {
|
16
|
+
"$ref": "#/definitions/Contract"
|
17
|
+
}
|
18
|
+
}
|
19
|
+
}
|
20
|
+
},
|
4
21
|
"User": {
|
5
22
|
"type": "object",
|
6
23
|
"properties": {
|
@@ -99,6 +116,18 @@
|
|
99
116
|
"type": "null"
|
100
117
|
}
|
101
118
|
]
|
119
|
+
},
|
120
|
+
"contracts": {
|
121
|
+
"type": "array",
|
122
|
+
"items": {
|
123
|
+
"$ref": "#/definitions/Contract"
|
124
|
+
}
|
125
|
+
},
|
126
|
+
"gigs": {
|
127
|
+
"type": "array",
|
128
|
+
"items": {
|
129
|
+
"$ref": "#/definitions/Gig"
|
130
|
+
}
|
102
131
|
}
|
103
132
|
}
|
104
133
|
},
|
@@ -270,20 +299,26 @@
|
|
270
299
|
"$ref": "#/definitions/Dropout"
|
271
300
|
}
|
272
301
|
},
|
302
|
+
"gigs": {
|
303
|
+
"type": "array",
|
304
|
+
"items": {
|
305
|
+
"$ref": "#/definitions/Gig"
|
306
|
+
}
|
307
|
+
},
|
273
308
|
"links": {
|
274
309
|
"type": "array",
|
275
310
|
"items": {
|
276
311
|
"$ref": "#/definitions/CohortLink"
|
277
312
|
}
|
278
313
|
},
|
279
|
-
"legacySlug": {
|
280
|
-
"type": "string"
|
281
|
-
},
|
282
314
|
"signupUsers": {
|
283
315
|
"type": "array",
|
284
316
|
"items": {
|
285
317
|
"$ref": "#/definitions/User"
|
286
318
|
}
|
319
|
+
},
|
320
|
+
"legacySlug": {
|
321
|
+
"type": "string"
|
287
322
|
}
|
288
323
|
}
|
289
324
|
},
|
@@ -307,7 +342,7 @@
|
|
307
342
|
"null"
|
308
343
|
]
|
309
344
|
},
|
310
|
-
"
|
345
|
+
"legacyStudentCode": {
|
311
346
|
"type": [
|
312
347
|
"string",
|
313
348
|
"null"
|
@@ -415,10 +450,115 @@
|
|
415
450
|
"$ref": "#/definitions/User"
|
416
451
|
}
|
417
452
|
}
|
453
|
+
},
|
454
|
+
"Contract": {
|
455
|
+
"type": "object",
|
456
|
+
"properties": {
|
457
|
+
"id": {
|
458
|
+
"type": "integer"
|
459
|
+
},
|
460
|
+
"createdAt": {
|
461
|
+
"type": "string",
|
462
|
+
"format": "date-time"
|
463
|
+
},
|
464
|
+
"updatedAt": {
|
465
|
+
"type": "string",
|
466
|
+
"format": "date-time"
|
467
|
+
},
|
468
|
+
"createdBy": {
|
469
|
+
"type": "string"
|
470
|
+
},
|
471
|
+
"start": {
|
472
|
+
"type": "string",
|
473
|
+
"format": "date-time"
|
474
|
+
},
|
475
|
+
"end": {
|
476
|
+
"type": [
|
477
|
+
"string",
|
478
|
+
"null"
|
479
|
+
],
|
480
|
+
"format": "date-time"
|
481
|
+
},
|
482
|
+
"hoursPerWeek": {
|
483
|
+
"type": "integer"
|
484
|
+
},
|
485
|
+
"isEmployment": {
|
486
|
+
"type": "boolean"
|
487
|
+
},
|
488
|
+
"feeAmount": {
|
489
|
+
"type": "integer"
|
490
|
+
},
|
491
|
+
"user": {
|
492
|
+
"$ref": "#/definitions/User"
|
493
|
+
},
|
494
|
+
"country": {
|
495
|
+
"$ref": "#/definitions/Country"
|
496
|
+
},
|
497
|
+
"gigs": {
|
498
|
+
"type": "array",
|
499
|
+
"items": {
|
500
|
+
"$ref": "#/definitions/Gig"
|
501
|
+
}
|
502
|
+
}
|
503
|
+
}
|
504
|
+
},
|
505
|
+
"Gig": {
|
506
|
+
"type": "object",
|
507
|
+
"properties": {
|
508
|
+
"id": {
|
509
|
+
"type": "integer"
|
510
|
+
},
|
511
|
+
"createdAt": {
|
512
|
+
"type": "string",
|
513
|
+
"format": "date-time"
|
514
|
+
},
|
515
|
+
"updatedAt": {
|
516
|
+
"type": "string",
|
517
|
+
"format": "date-time"
|
518
|
+
},
|
519
|
+
"createdBy": {
|
520
|
+
"type": "string"
|
521
|
+
},
|
522
|
+
"start": {
|
523
|
+
"type": "string",
|
524
|
+
"format": "date-time"
|
525
|
+
},
|
526
|
+
"end": {
|
527
|
+
"type": [
|
528
|
+
"string",
|
529
|
+
"null"
|
530
|
+
],
|
531
|
+
"format": "date-time"
|
532
|
+
},
|
533
|
+
"hoursPerWeek": {
|
534
|
+
"type": "integer"
|
535
|
+
},
|
536
|
+
"role": {
|
537
|
+
"type": "string",
|
538
|
+
"enum": [
|
539
|
+
"bm",
|
540
|
+
"pdc",
|
541
|
+
"js",
|
542
|
+
"ux"
|
543
|
+
]
|
544
|
+
},
|
545
|
+
"user": {
|
546
|
+
"$ref": "#/definitions/User"
|
547
|
+
},
|
548
|
+
"contract": {
|
549
|
+
"$ref": "#/definitions/Contract"
|
550
|
+
},
|
551
|
+
"cohort": {
|
552
|
+
"$ref": "#/definitions/Cohort"
|
553
|
+
}
|
554
|
+
}
|
418
555
|
}
|
419
556
|
},
|
420
557
|
"type": "object",
|
421
558
|
"properties": {
|
559
|
+
"country": {
|
560
|
+
"$ref": "#/definitions/Country"
|
561
|
+
},
|
422
562
|
"user": {
|
423
563
|
"$ref": "#/definitions/User"
|
424
564
|
},
|
@@ -442,6 +582,12 @@
|
|
442
582
|
},
|
443
583
|
"dropout": {
|
444
584
|
"$ref": "#/definitions/Dropout"
|
585
|
+
},
|
586
|
+
"contract": {
|
587
|
+
"$ref": "#/definitions/Contract"
|
588
|
+
},
|
589
|
+
"gig": {
|
590
|
+
"$ref": "#/definitions/Gig"
|
445
591
|
}
|
446
592
|
}
|
447
593
|
}
|
@@ -1,31 +0,0 @@
|
|
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.spec.js
DELETED
@@ -1,112 +0,0 @@
|
|
1
|
-
import { initializeApp } from 'firebase/app';
|
2
|
-
import {
|
3
|
-
getAuth,
|
4
|
-
onAuthStateChanged,
|
5
|
-
signInWithEmailAndPassword,
|
6
|
-
signOut,
|
7
|
-
} from 'firebase/auth';
|
8
|
-
import { createClient } from './lib/client.js';
|
9
|
-
import { createApp } from './index.js';
|
10
|
-
|
11
|
-
jest.mock('./lib/client.js');
|
12
|
-
|
13
|
-
beforeEach(() => {
|
14
|
-
initializeApp.mockClear();
|
15
|
-
onAuthStateChanged.mockClear();
|
16
|
-
signInWithEmailAndPassword.mockClear();
|
17
|
-
});
|
18
|
-
|
19
|
-
describe('createApp', () => {
|
20
|
-
it('should invoke firebase\'s initializaApp', () => {
|
21
|
-
createApp();
|
22
|
-
expect(initializeApp).toHaveBeenCalledTimes(1);
|
23
|
-
expect(initializeApp.mock.calls[0]).toMatchSnapshot();
|
24
|
-
});
|
25
|
-
});
|
26
|
-
|
27
|
-
describe('app.auth.onChange', () => {
|
28
|
-
it('should listen to firebase\'s onAuthStateChanged and notify subscribers when not authenticated', (done) => {
|
29
|
-
const { auth } = createApp();
|
30
|
-
|
31
|
-
onAuthStateChanged.mockImplementationOnce((_, cb) => {
|
32
|
-
cb();
|
33
|
-
return () => { };
|
34
|
-
});
|
35
|
-
|
36
|
-
auth.onChange(({ authUser, user }) => {
|
37
|
-
expect(authUser).toBeNull();
|
38
|
-
expect(user).toBeNull();
|
39
|
-
done();
|
40
|
-
});
|
41
|
-
});
|
42
|
-
|
43
|
-
it('should fetch user from db when authenticated and add isStaff, isManager, etc', (done) => {
|
44
|
-
const { auth } = createApp();
|
45
|
-
const mockAuthUser = { uid: 'xxx', getIdToken: () => 'token' };
|
46
|
-
const userMock = { uid: 'xxx', email: 'foo@bar.baz' };
|
47
|
-
|
48
|
-
onAuthStateChanged.mockImplementationOnce((_, cb) => {
|
49
|
-
cb(mockAuthUser);
|
50
|
-
return () => { };
|
51
|
-
});
|
52
|
-
|
53
|
-
createClient().mockResolvedValueOnce(userMock);
|
54
|
-
|
55
|
-
auth.onChange(({ authUser, user }) => {
|
56
|
-
expect(authUser).toEqual(mockAuthUser);
|
57
|
-
expect(user).toEqual({
|
58
|
-
...userMock,
|
59
|
-
isStaff: false,
|
60
|
-
isManager: false,
|
61
|
-
isFinance: false,
|
62
|
-
isAdmin: false,
|
63
|
-
});
|
64
|
-
done();
|
65
|
-
});
|
66
|
-
});
|
67
|
-
|
68
|
-
it('should log error and reset state (authUser = null, user = null) when fetch user from db fails', (done) => {
|
69
|
-
const { auth } = createApp();
|
70
|
-
const mockAuthUser = { uid: 'xxx', getIdToken: () => 'token' };
|
71
|
-
const spy = jest.spyOn(console, 'error').mockImplementationOnce(() => { })
|
72
|
-
const error = new Error('OMG');
|
73
|
-
|
74
|
-
onAuthStateChanged.mockImplementationOnce((_, cb) => {
|
75
|
-
cb(mockAuthUser);
|
76
|
-
return () => { };
|
77
|
-
});
|
78
|
-
|
79
|
-
createClient().mockRejectedValueOnce(error);
|
80
|
-
|
81
|
-
auth.onChange(({ authUser, user }) => {
|
82
|
-
expect(authUser).toBeNull();
|
83
|
-
expect(user).toBeNull();
|
84
|
-
expect(spy).toHaveBeenCalledWith(error);
|
85
|
-
spy.mockRestore();
|
86
|
-
done();
|
87
|
-
});
|
88
|
-
});
|
89
|
-
});
|
90
|
-
|
91
|
-
describe('app.auth.signIn', () => {
|
92
|
-
it('should delegate to firebase\'s signInWithEmailAndPassword', async () => {
|
93
|
-
const { auth } = createApp();
|
94
|
-
const email = 'foo@bar.baz';
|
95
|
-
const pass = 'secret';
|
96
|
-
|
97
|
-
await auth.signIn(email, pass);
|
98
|
-
|
99
|
-
expect(signInWithEmailAndPassword).toHaveBeenCalledTimes(1);
|
100
|
-
expect(signInWithEmailAndPassword).toHaveBeenCalledWith({}, email, pass);
|
101
|
-
});
|
102
|
-
});
|
103
|
-
|
104
|
-
describe('app.auth.signOut', () => {
|
105
|
-
it('should delegate to firebase\'s signOut', async () => {
|
106
|
-
const { auth } = createApp();
|
107
|
-
|
108
|
-
await auth.signOut();
|
109
|
-
|
110
|
-
expect(signOut).toHaveBeenCalledTimes(1);
|
111
|
-
});
|
112
|
-
});
|
package/lib/client.spec.js
DELETED
@@ -1,77 +0,0 @@
|
|
1
|
-
import { createClient } from './client.js';
|
2
|
-
|
3
|
-
describe('client', () => {
|
4
|
-
|
5
|
-
beforeAll(() => window.fetch = jest.fn());
|
6
|
-
|
7
|
-
afterEach(() => {
|
8
|
-
window.fetch.mockClear();
|
9
|
-
jest.restoreAllMocks();
|
10
|
-
});
|
11
|
-
|
12
|
-
it('should delegate to global fetch and add mode:cors and parse JSON resp when OK', async () => {
|
13
|
-
window.fetch.mockResolvedValue({
|
14
|
-
json: jest.fn().mockResolvedValue({ ok: true }),
|
15
|
-
});
|
16
|
-
const baseUrl = 'http://foo.bar';
|
17
|
-
const client = createClient(baseUrl);
|
18
|
-
expect(await client('/')).toEqual({ ok: true });
|
19
|
-
expect(window.fetch).toHaveBeenCalledTimes(1);
|
20
|
-
expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
|
21
|
-
mode: 'cors',
|
22
|
-
headers: {},
|
23
|
-
});
|
24
|
-
});
|
25
|
-
|
26
|
-
it('should reject when response.status > 202', async () => {
|
27
|
-
window.fetch.mockResolvedValue({
|
28
|
-
status: 400,
|
29
|
-
json: jest.fn().mockResolvedValue({ ok: false }),
|
30
|
-
});
|
31
|
-
const baseUrl = 'http://foo.bar';
|
32
|
-
const client = createClient(baseUrl);
|
33
|
-
await expect(client('/')).rejects.toThrow('HTTP Error 400');
|
34
|
-
expect(window.fetch).toHaveBeenCalledTimes(1);
|
35
|
-
expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
|
36
|
-
mode: 'cors',
|
37
|
-
headers: {},
|
38
|
-
});
|
39
|
-
});
|
40
|
-
|
41
|
-
it('should add token when authUser present', async () => {
|
42
|
-
window.fetch.mockResolvedValue({
|
43
|
-
json: jest.fn().mockResolvedValue({ ok: true }),
|
44
|
-
});
|
45
|
-
const baseUrl = 'http://foo.bar';
|
46
|
-
const user = { getIdToken: jest.fn().mockReturnValueOnce('xxx') };
|
47
|
-
const client = createClient(baseUrl, user);
|
48
|
-
expect(await client('/')).toEqual({ ok: true });
|
49
|
-
expect(window.fetch).toHaveBeenCalledTimes(1);
|
50
|
-
expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
|
51
|
-
mode: 'cors',
|
52
|
-
headers: {
|
53
|
-
authorization: 'Bearer xxx',
|
54
|
-
},
|
55
|
-
});
|
56
|
-
expect(user.getIdToken).toHaveBeenCalledTimes(1);
|
57
|
-
});
|
58
|
-
|
59
|
-
it('should add content-type header and stringify body when necessary', async () => {
|
60
|
-
window.fetch.mockResolvedValue({
|
61
|
-
json: jest.fn().mockResolvedValue({ ok: true }),
|
62
|
-
});
|
63
|
-
const baseUrl = 'http://foo.bar';
|
64
|
-
const client = createClient(baseUrl);
|
65
|
-
const body = { foo: 'bar' };
|
66
|
-
expect(await client('/', { method: 'POST', body })).toEqual({ ok: true });
|
67
|
-
expect(window.fetch).toHaveBeenCalledTimes(1);
|
68
|
-
expect(window.fetch).toHaveBeenCalledWith(`${baseUrl}/`, {
|
69
|
-
mode: 'cors',
|
70
|
-
headers: {
|
71
|
-
'content-type': 'application/json',
|
72
|
-
},
|
73
|
-
method: 'POST',
|
74
|
-
body: JSON.stringify(body),
|
75
|
-
});
|
76
|
-
});
|
77
|
-
});
|
package/lib/currencies.js
DELETED
package/lib/model.spec.js
DELETED
@@ -1,243 +0,0 @@
|
|
1
|
-
import { createModel } from './model.js';
|
2
|
-
import { createClient } from './client.js';
|
3
|
-
|
4
|
-
jest.mock('./client.js');
|
5
|
-
|
6
|
-
beforeEach(() => createClient().mockClear());
|
7
|
-
|
8
|
-
describe('model.schema', () => {
|
9
|
-
it('should be empty object by default', () => {
|
10
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
11
|
-
expect(model.schema).toEqual({});
|
12
|
-
});
|
13
|
-
|
14
|
-
it('should get the model\'s schema', () => {
|
15
|
-
const schema = {
|
16
|
-
properties: {
|
17
|
-
a: { type: 'string' },
|
18
|
-
},
|
19
|
-
};
|
20
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
21
|
-
expect(model.schema).toEqual(schema);
|
22
|
-
});
|
23
|
-
});
|
24
|
-
|
25
|
-
describe('model.findMany(options)', () => {
|
26
|
-
it('should send GET request', async () => {
|
27
|
-
const schema = {
|
28
|
-
properties: {
|
29
|
-
id: { type: 'integer' },
|
30
|
-
createdAt: { type: 'string', format: 'date-time' },
|
31
|
-
},
|
32
|
-
};
|
33
|
-
const now = new Date();
|
34
|
-
const client = createClient().mockResolvedValue([{
|
35
|
-
id: 1,
|
36
|
-
createdAt: now.toISOString(),
|
37
|
-
}]);
|
38
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
39
|
-
const results = await model.findMany();
|
40
|
-
expect(results).toEqual([{ id: 1, createdAt: now }]);
|
41
|
-
expect(results[0].createdAt instanceof Date).toBe(true);
|
42
|
-
expect(client).toHaveBeenCalledTimes(1);
|
43
|
-
expect(client).toHaveBeenCalledWith('/foo');
|
44
|
-
});
|
45
|
-
});
|
46
|
-
|
47
|
-
describe('model.findById(id, options)', () => {
|
48
|
-
it('should send GET request', async () => {
|
49
|
-
const client = createClient().mockResolvedValue({});
|
50
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
51
|
-
expect(await model.findById(1)).toEqual({});
|
52
|
-
expect(client).toHaveBeenCalledTimes(1);
|
53
|
-
expect(client).toHaveBeenCalledWith('/foo/1');
|
54
|
-
});
|
55
|
-
});
|
56
|
-
|
57
|
-
describe('model.create(options)', () => {
|
58
|
-
it('should send POST request', async () => {
|
59
|
-
const client = createClient().mockResolvedValue({});
|
60
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
61
|
-
expect(await model.create({ data: { ok: true } })).toEqual({});
|
62
|
-
expect(client).toHaveBeenCalledTimes(1);
|
63
|
-
expect(client).toHaveBeenCalledWith('/foo', {
|
64
|
-
body: { data: { ok: true } },
|
65
|
-
method: 'POST',
|
66
|
-
});
|
67
|
-
});
|
68
|
-
});
|
69
|
-
|
70
|
-
describe('model.update(options)', () => {
|
71
|
-
it('should send POST request', async () => {
|
72
|
-
const client = createClient().mockResolvedValue({});
|
73
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
74
|
-
expect(await model.update({ where: { id: 1 }, data: { ok: true } })).toEqual({});
|
75
|
-
expect(client).toHaveBeenCalledTimes(1);
|
76
|
-
expect(client).toHaveBeenCalledWith('/foo/1', {
|
77
|
-
body: { where: { id: 1 }, data: { ok: true } },
|
78
|
-
method: 'PUT',
|
79
|
-
});
|
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
|
-
});
|
109
|
-
});
|
110
|
-
|
111
|
-
describe('model.upsert(options)', () => {
|
112
|
-
it('should send POST request when new row', async () => {
|
113
|
-
const client = createClient().mockResolvedValue({});
|
114
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
115
|
-
const result = await model.upsert({
|
116
|
-
where: {},
|
117
|
-
create: { ok: true },
|
118
|
-
});
|
119
|
-
expect(result).toEqual({});
|
120
|
-
expect(client).toHaveBeenCalledTimes(1);
|
121
|
-
expect(client).toHaveBeenCalledWith('/foo', {
|
122
|
-
body: { data: { ok: true } },
|
123
|
-
method: 'POST',
|
124
|
-
});
|
125
|
-
});
|
126
|
-
|
127
|
-
it('should send PUT request when existing row', async () => {
|
128
|
-
const client = createClient().mockResolvedValue({});
|
129
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
130
|
-
const result = await model.upsert({
|
131
|
-
where: { id: 1 },
|
132
|
-
update: { ok: true },
|
133
|
-
});
|
134
|
-
expect(result).toEqual({});
|
135
|
-
expect(client).toHaveBeenCalledTimes(1);
|
136
|
-
expect(client).toHaveBeenCalledWith('/foo/1', {
|
137
|
-
body: {
|
138
|
-
where: { id: 1 },
|
139
|
-
data: { ok: true },
|
140
|
-
},
|
141
|
-
method: 'PUT',
|
142
|
-
});
|
143
|
-
});
|
144
|
-
});
|
145
|
-
|
146
|
-
describe('model.delete(id)', () => {
|
147
|
-
it('should send POST request', async () => {
|
148
|
-
const client = createClient().mockResolvedValue({});
|
149
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
150
|
-
expect(await model.delete(1)).toEqual({});
|
151
|
-
expect(client).toHaveBeenCalledTimes(1);
|
152
|
-
expect(client).toHaveBeenCalledWith('/foo/1', {
|
153
|
-
method: 'DELETE',
|
154
|
-
});
|
155
|
-
});
|
156
|
-
});
|
157
|
-
|
158
|
-
describe('model.validateAttr(key, value)', () => {
|
159
|
-
it('should not validate when no schema', () => {
|
160
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
161
|
-
expect(model.validateAttr('a', 1)).toBeUndefined();
|
162
|
-
});
|
163
|
-
|
164
|
-
it('should validate text when required', () => {
|
165
|
-
const schema = {
|
166
|
-
properties: {
|
167
|
-
a: { type: 'string' },
|
168
|
-
},
|
169
|
-
};
|
170
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
171
|
-
expect(model.validateAttr('a', '')).toBe('Field is required');
|
172
|
-
});
|
173
|
-
|
174
|
-
it('should validate number when required', () => {
|
175
|
-
const schema = {
|
176
|
-
properties: {
|
177
|
-
a: { type: 'integer', required: true },
|
178
|
-
},
|
179
|
-
};
|
180
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
181
|
-
expect(model.validateAttr('a', 'foo')).toBe('Invalid number format');
|
182
|
-
});
|
183
|
-
|
184
|
-
it('should validate enum', () => {
|
185
|
-
const schema = {
|
186
|
-
properties: {
|
187
|
-
a: { type: 'integer', enum: [1, 2, 3] },
|
188
|
-
},
|
189
|
-
};
|
190
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
191
|
-
expect(model.validateAttr('a', 'foo')).toBe('foo is not one of: 1,2,3');
|
192
|
-
});
|
193
|
-
|
194
|
-
it('should allow empty enum when not required', () => {
|
195
|
-
const schema = {
|
196
|
-
properties: {
|
197
|
-
a: { type: ['integer', 'null'], enum: [1, 2, 3] },
|
198
|
-
},
|
199
|
-
};
|
200
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
201
|
-
expect(model.validateAttr('a', null)).toBeUndefined();
|
202
|
-
expect(model.validateAttr('a', undefined)).toBeUndefined();
|
203
|
-
expect(model.validateAttr('a', '')).toBeUndefined();
|
204
|
-
});
|
205
|
-
});
|
206
|
-
|
207
|
-
describe('model.validate(attributes)', () => {
|
208
|
-
it('should not validate when no schema', () => {
|
209
|
-
const model = createModel('http://1.2.3.4', {}, 'foo');
|
210
|
-
expect(model.validate({ a: 1 })).toEqual({});
|
211
|
-
});
|
212
|
-
|
213
|
-
it('should validate when schema present', () => {
|
214
|
-
const schema = {
|
215
|
-
properties: {
|
216
|
-
a: { type: 'string' },
|
217
|
-
b: { type: 'integer' },
|
218
|
-
},
|
219
|
-
};
|
220
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
221
|
-
expect(model.validate({ a: '', b: 1 })).toEqual({ a: 'Field is required' });
|
222
|
-
});
|
223
|
-
|
224
|
-
it('should validate number when required', () => {
|
225
|
-
const schema = {
|
226
|
-
properties: {
|
227
|
-
a: { type: 'integer' },
|
228
|
-
},
|
229
|
-
};
|
230
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
231
|
-
expect(model.validateAttr('a', 'foo')).toBe('Invalid number format');
|
232
|
-
});
|
233
|
-
|
234
|
-
it('should validate enum', () => {
|
235
|
-
const schema = {
|
236
|
-
properties: {
|
237
|
-
a: { type: 'integer', enum: [1, 2, 3] },
|
238
|
-
},
|
239
|
-
};
|
240
|
-
const model = createModel('http://1.2.3.4', {}, 'foo', schema);
|
241
|
-
expect(model.validateAttr('a', 'foo')).toBe('foo is not one of: 1,2,3');
|
242
|
-
});
|
243
|
-
});
|
package/lib/team.js
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
import { createModels, extendSchemaDefinitions } from './model';
|
2
|
-
import currencies from './currencies';
|
3
|
-
import roles from './roles';
|
4
|
-
import schema from '../schemas/team.json';
|
5
|
-
|
6
|
-
const extended = {
|
7
|
-
Country: {
|
8
|
-
plural: 'countries',
|
9
|
-
},
|
10
|
-
Contract: {
|
11
|
-
inputProps: [
|
12
|
-
'uid',
|
13
|
-
'countryCode',
|
14
|
-
'currency',
|
15
|
-
'isEmployment',
|
16
|
-
'feeBasis',
|
17
|
-
'feeAmount',
|
18
|
-
'hoursPerWeek',
|
19
|
-
'start',
|
20
|
-
'end',
|
21
|
-
],
|
22
|
-
properties: {
|
23
|
-
countryCode: {
|
24
|
-
enum: ['BR', 'CL', 'CO', 'MX', 'PE'],
|
25
|
-
},
|
26
|
-
currency: {
|
27
|
-
enum: currencies,
|
28
|
-
},
|
29
|
-
},
|
30
|
-
},
|
31
|
-
Gig: {
|
32
|
-
inputProps: [
|
33
|
-
// 'contract',
|
34
|
-
'cohortId',
|
35
|
-
'uid',
|
36
|
-
'role',
|
37
|
-
'hoursPerWeek',
|
38
|
-
'start',
|
39
|
-
'end',
|
40
|
-
],
|
41
|
-
properties: {
|
42
|
-
role: {
|
43
|
-
enum: roles,
|
44
|
-
},
|
45
|
-
},
|
46
|
-
},
|
47
|
-
};
|
48
|
-
|
49
|
-
export const createAPI = (url, state) => createModels(url, state, {
|
50
|
-
...schema,
|
51
|
-
definitions: extendSchemaDefinitions(schema, extended),
|
52
|
-
});
|
package/schemas/team.json
DELETED
@@ -1,146 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
-
"definitions": {
|
4
|
-
"Country": {
|
5
|
-
"type": "object",
|
6
|
-
"properties": {
|
7
|
-
"code": {
|
8
|
-
"type": "string"
|
9
|
-
},
|
10
|
-
"name": {
|
11
|
-
"type": "string"
|
12
|
-
},
|
13
|
-
"contracts": {
|
14
|
-
"type": "array",
|
15
|
-
"items": {
|
16
|
-
"$ref": "#/definitions/Contract"
|
17
|
-
}
|
18
|
-
}
|
19
|
-
}
|
20
|
-
},
|
21
|
-
"Contract": {
|
22
|
-
"type": "object",
|
23
|
-
"properties": {
|
24
|
-
"id": {
|
25
|
-
"type": "integer"
|
26
|
-
},
|
27
|
-
"createdAt": {
|
28
|
-
"type": "string",
|
29
|
-
"format": "date-time"
|
30
|
-
},
|
31
|
-
"updatedAt": {
|
32
|
-
"type": "string",
|
33
|
-
"format": "date-time"
|
34
|
-
},
|
35
|
-
"createdBy": {
|
36
|
-
"type": "string"
|
37
|
-
},
|
38
|
-
"uid": {
|
39
|
-
"type": "string"
|
40
|
-
},
|
41
|
-
"isEmployment": {
|
42
|
-
"type": "boolean"
|
43
|
-
},
|
44
|
-
"currency": {
|
45
|
-
"type": "string"
|
46
|
-
},
|
47
|
-
"feeBasis": {
|
48
|
-
"type": "string",
|
49
|
-
"enum": [
|
50
|
-
"HOURLY",
|
51
|
-
"MONTHLY"
|
52
|
-
]
|
53
|
-
},
|
54
|
-
"feeAmount": {
|
55
|
-
"type": "integer"
|
56
|
-
},
|
57
|
-
"hoursPerWeek": {
|
58
|
-
"type": "integer"
|
59
|
-
},
|
60
|
-
"start": {
|
61
|
-
"type": "string",
|
62
|
-
"format": "date-time"
|
63
|
-
},
|
64
|
-
"end": {
|
65
|
-
"type": [
|
66
|
-
"string",
|
67
|
-
"null"
|
68
|
-
],
|
69
|
-
"format": "date-time"
|
70
|
-
},
|
71
|
-
"country": {
|
72
|
-
"$ref": "#/definitions/Country"
|
73
|
-
},
|
74
|
-
"gigs": {
|
75
|
-
"type": "array",
|
76
|
-
"items": {
|
77
|
-
"$ref": "#/definitions/Gig"
|
78
|
-
}
|
79
|
-
}
|
80
|
-
}
|
81
|
-
},
|
82
|
-
"Gig": {
|
83
|
-
"type": "object",
|
84
|
-
"properties": {
|
85
|
-
"id": {
|
86
|
-
"type": "integer"
|
87
|
-
},
|
88
|
-
"createdAt": {
|
89
|
-
"type": "string",
|
90
|
-
"format": "date-time"
|
91
|
-
},
|
92
|
-
"updatedAt": {
|
93
|
-
"type": "string",
|
94
|
-
"format": "date-time"
|
95
|
-
},
|
96
|
-
"createdBy": {
|
97
|
-
"type": "string"
|
98
|
-
},
|
99
|
-
"role": {
|
100
|
-
"type": "string",
|
101
|
-
"enum": [
|
102
|
-
"bm",
|
103
|
-
"pdc",
|
104
|
-
"js",
|
105
|
-
"ux"
|
106
|
-
]
|
107
|
-
},
|
108
|
-
"hoursPerWeek": {
|
109
|
-
"type": "integer"
|
110
|
-
},
|
111
|
-
"start": {
|
112
|
-
"type": "string",
|
113
|
-
"format": "date-time"
|
114
|
-
},
|
115
|
-
"end": {
|
116
|
-
"type": [
|
117
|
-
"string",
|
118
|
-
"null"
|
119
|
-
],
|
120
|
-
"format": "date-time"
|
121
|
-
},
|
122
|
-
"uid": {
|
123
|
-
"type": "string"
|
124
|
-
},
|
125
|
-
"cohortId": {
|
126
|
-
"type": "integer"
|
127
|
-
},
|
128
|
-
"contract": {
|
129
|
-
"$ref": "#/definitions/Contract"
|
130
|
-
}
|
131
|
-
}
|
132
|
-
}
|
133
|
-
},
|
134
|
-
"type": "object",
|
135
|
-
"properties": {
|
136
|
-
"country": {
|
137
|
-
"$ref": "#/definitions/Country"
|
138
|
-
},
|
139
|
-
"contract": {
|
140
|
-
"$ref": "#/definitions/Contract"
|
141
|
-
},
|
142
|
-
"gig": {
|
143
|
-
"$ref": "#/definitions/Gig"
|
144
|
-
}
|
145
|
-
}
|
146
|
-
}
|