@jcbuisson/express-x 1.7.11 → 1.7.12
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/package.json +1 -3
- package/src/common-hooks.mjs +15 -11
- package/src/index.mjs +1 -2
- package/src/server.mjs +3 -2
- package/prisma/schema.prisma +0 -22
- package/test/index.test.js +0 -169
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jcbuisson/express-x",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.12",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.mjs",
|
|
@@ -12,14 +12,12 @@
|
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"private": false,
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "node --test",
|
|
16
15
|
"generate": "npx prisma generate",
|
|
17
16
|
"migrate": "npx prisma migrate dev --name init"
|
|
18
17
|
},
|
|
19
18
|
"keywords": [],
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"@prisma/client": "^4.10.1",
|
|
22
|
-
"axios": "^1.4.0",
|
|
23
21
|
"bcryptjs": "^2.4.3",
|
|
24
22
|
"config": "^3.3.9",
|
|
25
23
|
"express": "^4.18.2",
|
package/src/common-hooks.mjs
CHANGED
|
@@ -4,14 +4,20 @@ import bcrypt from 'bcryptjs'
|
|
|
4
4
|
import { getConnectionDataItem } from './context.mjs'
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/*
|
|
8
|
+
* Hash password of user record
|
|
9
|
+
* To be used as an 'after' hook for users service methods
|
|
10
|
+
*/
|
|
8
11
|
export const hashPassword = (passwordField) => async (context) => {
|
|
9
|
-
|
|
10
|
-
|
|
12
|
+
const user = context.result
|
|
13
|
+
user[passwordField] = await bcrypt.hash(user[passwordField], 5)
|
|
11
14
|
return context
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Remove `field` from `context.result`
|
|
20
|
+
*/
|
|
15
21
|
export function protect(field) {
|
|
16
22
|
return async (context) => {
|
|
17
23
|
if (Array.isArray(context.result)) {
|
|
@@ -25,13 +31,11 @@ export function protect(field) {
|
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
34
|
+
/*
|
|
35
|
+
* Check if the 'expireAt' key in connection data is met
|
|
36
|
+
* If it is met, throw an error (which will be sent back to the calling server or client) and reset connection data
|
|
37
|
+
* If not, do nothing. If needed, an application-level hook may automatically extend the expiration data at each service call
|
|
38
|
+
*/
|
|
35
39
|
export const isNotExpired = async (context) => {
|
|
36
40
|
const expireAt = await getConnectionDataItem(context, 'expireAt')
|
|
37
41
|
if (expireAt) {
|
package/src/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { expressX } from './server.mjs'
|
|
3
|
-
import { hashPassword, protect,
|
|
3
|
+
import { hashPassword, protect, isNotExpired } from './common-hooks.mjs'
|
|
4
4
|
import { getContextConnection, resetConnection, getConnectionDataItem, setConnectionDataItem, removeConnectionDataItem, sendServiceEventToClient } from './context.mjs'
|
|
5
5
|
|
|
6
6
|
export {
|
|
@@ -17,6 +17,5 @@ export {
|
|
|
17
17
|
|
|
18
18
|
hashPassword,
|
|
19
19
|
protect,
|
|
20
|
-
isAuthenticated,
|
|
21
20
|
isNotExpired,
|
|
22
21
|
}
|
package/src/server.mjs
CHANGED
|
@@ -97,12 +97,13 @@ export function expressX(prisma, config) {
|
|
|
97
97
|
// `context` is the context of execution (transport type, connection, app)
|
|
98
98
|
// `args` is the list of arguments of the method
|
|
99
99
|
service['__' + methodName] = async (context, ...args) => {
|
|
100
|
+
// put args into context
|
|
100
101
|
context.args = args
|
|
101
102
|
|
|
102
103
|
// if a hook or the method throws an error, it will be caught by `socket.on('client-request'` (ws)
|
|
103
104
|
// or by express (http) and the client will get a rejected promise
|
|
104
105
|
|
|
105
|
-
// call 'before' hooks, modifying `context`
|
|
106
|
+
// call 'before' hooks, possibly modifying `context`
|
|
106
107
|
const beforeAppHooks = appHooks?.before || []
|
|
107
108
|
const beforeMethodHooks = service?.hooks?.before && service.hooks.before[methodName] || []
|
|
108
109
|
const beforeAllHooks = service?.hooks?.before?.all || []
|
|
@@ -115,7 +116,7 @@ export function expressX(prisma, config) {
|
|
|
115
116
|
// put result into context
|
|
116
117
|
context.result = result
|
|
117
118
|
|
|
118
|
-
// call 'after' hooks, modifying `context`
|
|
119
|
+
// call 'after' hooks, possibly modifying `context`
|
|
119
120
|
const afterMethodHooks = service?.hooks?.after && service.hooks.after[methodName] || []
|
|
120
121
|
const afterAllHooks = service?.hooks?.after?.all || []
|
|
121
122
|
const afterAppHooks = appHooks?.after || []
|
package/prisma/schema.prisma
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
generator client {
|
|
2
|
-
provider = "prisma-client-js"
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
model User {
|
|
6
|
-
id Int @default(autoincrement()) @id
|
|
7
|
-
name String
|
|
8
|
-
email String @unique
|
|
9
|
-
posts Post[]
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
model Post {
|
|
13
|
-
id Int @default(autoincrement()) @id
|
|
14
|
-
text String
|
|
15
|
-
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
16
|
-
authorId Int
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
datasource db {
|
|
20
|
-
provider = "sqlite"
|
|
21
|
-
url = "file:./dev.db"
|
|
22
|
-
}
|
package/test/index.test.js
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import bodyParser from 'body-parser'
|
|
3
|
-
import axios from 'axios'
|
|
4
|
-
import { PrismaClient } from '@prisma/client'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { describe, it, before, after, beforeEach, afterEach } from 'node:test'
|
|
8
|
-
import { strict as assert } from 'node:assert'
|
|
9
|
-
|
|
10
|
-
import { expressX } from '../src/index.mjs'
|
|
11
|
-
|
|
12
|
-
const prisma = new PrismaClient()
|
|
13
|
-
|
|
14
|
-
// `app` is a regular express application, enhanced with services and real-time features
|
|
15
|
-
const app = expressX(prisma)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
describe('ExpressX API (no running server)', () => {
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
app.createDatabaseService('User')
|
|
23
|
-
app.createDatabaseService('Post')
|
|
24
|
-
|
|
25
|
-
await app.service('User').deleteMany()
|
|
26
|
-
await app.service('Post').deleteMany()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it("can delete all users", async () => {
|
|
30
|
-
const res = await app.service('User').deleteMany()
|
|
31
|
-
console.log('res delete', res)
|
|
32
|
-
assert(res.count >= 0)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it("can create a user", async () => {
|
|
36
|
-
const user = await app.service('User').create({
|
|
37
|
-
data: {
|
|
38
|
-
name: "chris",
|
|
39
|
-
email: 'chris@mail.fr'
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
assert(user.name === 'chris')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it("can find a user by name", async () => {
|
|
46
|
-
const users = await app.service('User').findMany({
|
|
47
|
-
where: {
|
|
48
|
-
name: {
|
|
49
|
-
startsWith: "ch"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
})
|
|
53
|
-
assert(users.length > 0)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it("can find a unique user by email", async () => {
|
|
57
|
-
const chris = await app.service('User').findUnique({
|
|
58
|
-
where: {
|
|
59
|
-
email: "chris@mail.fr"
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
assert(chris.name === 'chris')
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
describe('HTTP/REST API', () => {
|
|
68
|
-
|
|
69
|
-
let chris
|
|
70
|
-
|
|
71
|
-
before(async () => {
|
|
72
|
-
console.log("before")
|
|
73
|
-
// add body parsers for http requests
|
|
74
|
-
app.use(bodyParser.json())
|
|
75
|
-
app.use(bodyParser.urlencoded({ extended: false }))
|
|
76
|
-
|
|
77
|
-
app.createDatabaseService('User')
|
|
78
|
-
app.createDatabaseService('Post')
|
|
79
|
-
|
|
80
|
-
await app.service('User').deleteMany()
|
|
81
|
-
await app.service('Post').deleteMany()
|
|
82
|
-
|
|
83
|
-
// add http/rest endpoints
|
|
84
|
-
app.addHttpRest('/api/user', app.service('User'))
|
|
85
|
-
app.addHttpRest('/api/post', app.service('Post'))
|
|
86
|
-
|
|
87
|
-
await new Promise((resolve) => {
|
|
88
|
-
app.server.listen(8008, () => {
|
|
89
|
-
console.log(`App listening at http://localhost:8008`)
|
|
90
|
-
resolve()
|
|
91
|
-
})
|
|
92
|
-
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
after(() => {
|
|
96
|
-
console.log("after")
|
|
97
|
-
app.server.close()
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it("can create a user", async () => {
|
|
101
|
-
const res = await axios.post('http://localhost:8008/api/user', {
|
|
102
|
-
name: "chris",
|
|
103
|
-
email: 'chris@mail.fr'
|
|
104
|
-
})
|
|
105
|
-
assert(res?.data?.name === 'chris')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
it("can find users", async () => {
|
|
109
|
-
const res = await axios.get('http://localhost:8008/api/user')
|
|
110
|
-
assert(res?.data?.length > 0)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it("can find a user by name", async () => {
|
|
114
|
-
const res = await axios.get('http://localhost:8008/api/user?name=chris')
|
|
115
|
-
assert(res?.data?.length > 0)
|
|
116
|
-
chris = res.data[0]
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it("can find a user by id", async () => {
|
|
120
|
-
const res = await axios.get(`http://localhost:8008/api/user/${chris.id}`)
|
|
121
|
-
assert(res?.data?.id === chris.id)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it("can patch a user", async () => {
|
|
125
|
-
const res = await axios.patch(`http://localhost:8008/api/user/${chris.id}`, {
|
|
126
|
-
name: "Christophe",
|
|
127
|
-
})
|
|
128
|
-
assert(res?.data?.name === "Christophe")
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it("can delete a user", async () => {
|
|
132
|
-
const res = await axios.delete(`http://localhost:8008/api/user/${chris.id}`)
|
|
133
|
-
assert(res?.data?.name === "Christophe")
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
after(async () => {
|
|
137
|
-
app.server.close()
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
describe('Error codes', () => {
|
|
143
|
-
|
|
144
|
-
before(async () => {
|
|
145
|
-
app.createDatabaseService('User')
|
|
146
|
-
app.createDatabaseService('Post')
|
|
147
|
-
|
|
148
|
-
await app.service('User').deleteMany()
|
|
149
|
-
await app.service('Post').deleteMany()
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it("can detect 'missing-service' error", async () => {
|
|
153
|
-
try {
|
|
154
|
-
await app.service('users').findMany({})
|
|
155
|
-
} catch(err) {
|
|
156
|
-
assert(err.code === 'missing-service')
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
// it("can detect 'missing-method' error", async () => {
|
|
161
|
-
// try {
|
|
162
|
-
// await app.service('User').dummyMethod({})
|
|
163
|
-
// } catch(err) {
|
|
164
|
-
// console.log('err', err)
|
|
165
|
-
// assert(err.code === 'missing-method')
|
|
166
|
-
// }
|
|
167
|
-
// })
|
|
168
|
-
|
|
169
|
-
})
|