@aaroncadillac/crudify-mongo 1.4.0 → 1.6.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/publish.yml +4 -1
- package/.github/workflows/test.yml +2 -2
- package/README.md +110 -0
- package/crudify.js +93 -18
- package/jest-mongodb-config.js +2 -1
- package/package.json +1 -1
- package/test/crudify.test.js +1 -4
|
@@ -10,9 +10,9 @@ jobs:
|
|
|
10
10
|
runs-on: ubuntu-latest
|
|
11
11
|
steps:
|
|
12
12
|
- name: Checkout repository
|
|
13
|
-
uses: actions/checkout@
|
|
13
|
+
uses: actions/checkout@v4
|
|
14
14
|
- name: Set up Node.js
|
|
15
|
-
uses: actions/setup-node@
|
|
15
|
+
uses: actions/setup-node@v4
|
|
16
16
|
with:
|
|
17
17
|
node-version: '20'
|
|
18
18
|
- name: Install dependencies
|
package/README.md
CHANGED
|
@@ -55,6 +55,116 @@ const routes = ( fastify, opts, done ) => {
|
|
|
55
55
|
})
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### Swagger auto-documentation
|
|
59
|
+
|
|
60
|
+
You can add Swagger auto-documentation to your API, using the [`@fastify/swagger`](https://github.com/fastify/fastify-swagger) and [`@fastify/swagger-ui`](https://github.com/fastify/fastify-swagger-ui) plugins.
|
|
61
|
+
|
|
62
|
+
Once you have those plugins installed, you can use the following code to add Swagger auto-documentation to your API:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
// server.js
|
|
66
|
+
|
|
67
|
+
import 'dotenv/config';
|
|
68
|
+
import Fastify from 'fastify'
|
|
69
|
+
import { dbConnection } from './db-connection.js';
|
|
70
|
+
import { routes } from './routes.js';
|
|
71
|
+
import * as Swagger from '@fastify/swagger'
|
|
72
|
+
import * as SwaggerUI from '@fastify/swagger-ui'
|
|
73
|
+
|
|
74
|
+
const fastify = Fastify({
|
|
75
|
+
logger: true
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
fastify.register(await dbConnection)
|
|
79
|
+
process.env.NODE_ENV == 'development' && fastify.register(Swagger) && await fastify.register(SwaggerUI, {
|
|
80
|
+
routePrefix: '/docs',
|
|
81
|
+
uiConfig: {
|
|
82
|
+
docExpansion: 'list',
|
|
83
|
+
deepLinking: true
|
|
84
|
+
},
|
|
85
|
+
uiHooks: {
|
|
86
|
+
onRequest: function (request, reply, next) { next() },
|
|
87
|
+
preHandler: function (request, reply, next) { next() }
|
|
88
|
+
},
|
|
89
|
+
staticCSP: true,
|
|
90
|
+
transformStaticCSP: (header) => header,
|
|
91
|
+
transformSpecification: (swaggerObject, request, reply) => { return swaggerObject },
|
|
92
|
+
transformSpecificationClone: true
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
fastify.register(routes)
|
|
96
|
+
|
|
97
|
+
fastify.listen({ port: process.env.PORT || 3000, host: process.env.HOST || 'localhost' }, function (err, address) {
|
|
98
|
+
if (err) {
|
|
99
|
+
fastify.log.error(err)
|
|
100
|
+
process.exit(1)
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
As you can see in the code above, you only need to import the `@fastify/swagger` and `@fastify/swagger-ui` plugins, and then use the `fastify.register` method to register the plugins., you even can customize your swagger documentation path and enable documentation only in development environment (recommended).
|
|
106
|
+
|
|
107
|
+
### Body and Response schema auto generation
|
|
108
|
+
|
|
109
|
+
To help you to use Swagger `crudify-mongo 1.5.0+` supports `body` and `response` schema auto generation, this feature is enable by default and you only need to install [`mongoose-schema-jsonschema`](https://github.com/DScheglov/mongoose-schema-jsonschema) to use it, this dependency helps you to use your mongoose model schema as a json schema compatible with fastify specification based on [AJV schema validation](https://ajv.js.org).
|
|
110
|
+
|
|
111
|
+
Here an example
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
// models/user.js
|
|
115
|
+
|
|
116
|
+
import mongoose from 'mongoose';
|
|
117
|
+
import paginate from 'mongoose-paginate-v2'; // Required for pagination
|
|
118
|
+
import extendMongoose from 'mongoose-schema-jsonschema'; // Required for body and response schema auto generation
|
|
119
|
+
|
|
120
|
+
extendMongoose(mongoose);
|
|
121
|
+
|
|
122
|
+
const Schema = mongoose.Schema;
|
|
123
|
+
|
|
124
|
+
const userSchema = new Schema({
|
|
125
|
+
name: {
|
|
126
|
+
type: String,
|
|
127
|
+
required: true,
|
|
128
|
+
},
|
|
129
|
+
age: {
|
|
130
|
+
type: Number,
|
|
131
|
+
required: true,
|
|
132
|
+
},
|
|
133
|
+
email: {
|
|
134
|
+
type: String,
|
|
135
|
+
required: true,
|
|
136
|
+
},
|
|
137
|
+
}, {
|
|
138
|
+
timestamps: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
userSchema.plugin(paginate);
|
|
142
|
+
|
|
143
|
+
const UserSchema = userSchema.jsonSchema();
|
|
144
|
+
const UserModel = mongoose.model('Users', userSchema);
|
|
145
|
+
|
|
146
|
+
export {
|
|
147
|
+
UserModel,
|
|
148
|
+
UserSchema
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then you need to add on the `JSONSchema` property of your fastify routes
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
// routes.js
|
|
156
|
+
import { Crudify } from '@aaroncadillac/crudify-mongo';
|
|
157
|
+
import { UserModel, UserSchema } from './models/user.js';
|
|
158
|
+
|
|
159
|
+
const routes = ( fastify, opts, done ) => {
|
|
160
|
+
fastify.register(Crudify, {
|
|
161
|
+
url: '/users',
|
|
162
|
+
Model: UserModel
|
|
163
|
+
JSONSchema: UserSchema
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
58
168
|
## Getting started
|
|
59
169
|
|
|
60
170
|
### Installation
|
package/crudify.js
CHANGED
|
@@ -1,21 +1,96 @@
|
|
|
1
|
-
function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
function removeKeys(obj, keysToRemove) {
|
|
2
|
+
if (typeof obj !== 'object' || obj === null) return;
|
|
3
|
+
|
|
4
|
+
for (const key in obj) {
|
|
5
|
+
if (keysToRemove.includes(key)) {
|
|
6
|
+
delete obj[key];
|
|
7
|
+
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
8
|
+
removeKeys(obj[key], keysToRemove);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
function addPostBody(opts) {
|
|
15
|
+
const schema = opts.JSONSchema
|
|
16
|
+
if ( schema ) {
|
|
17
|
+
const properties = {...schema.properties}
|
|
18
|
+
removeKeys(properties, ['x-ref'])
|
|
19
|
+
const newOpts = {
|
|
20
|
+
...opts,
|
|
21
|
+
schema: {
|
|
22
|
+
...opts.schema,
|
|
23
|
+
body: {
|
|
24
|
+
type: schema.type,
|
|
25
|
+
properties,
|
|
26
|
+
required: schema.required
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
delete newOpts.schema.body.properties._id
|
|
31
|
+
delete newOpts.schema.body.properties.createdAt
|
|
32
|
+
delete newOpts.schema.body.properties.updatedAt
|
|
33
|
+
return newOpts
|
|
34
|
+
} else {
|
|
35
|
+
return opts
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function addGetResponseUnitary(opts) {
|
|
40
|
+
const schema = opts.JSONSchema
|
|
41
|
+
if ( schema ) {
|
|
42
|
+
const newOpts = {
|
|
43
|
+
...opts,
|
|
44
|
+
schema: {
|
|
45
|
+
response: {
|
|
46
|
+
200: schema
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return newOpts
|
|
51
|
+
} else {
|
|
52
|
+
return opts
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function addGetResponseArray(opts) {
|
|
57
|
+
const schema = opts.JSONSchema
|
|
58
|
+
if ( schema ) {
|
|
59
|
+
const newOpts = {
|
|
60
|
+
...opts,
|
|
61
|
+
schema: {
|
|
62
|
+
response: {
|
|
63
|
+
200: {
|
|
64
|
+
type: 'array',
|
|
65
|
+
items: schema
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
querystring: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
filters: {
|
|
72
|
+
type: 'string'
|
|
73
|
+
},
|
|
74
|
+
pagination: {
|
|
75
|
+
type: 'string'
|
|
15
76
|
}
|
|
16
77
|
}
|
|
17
78
|
}
|
|
18
79
|
}
|
|
80
|
+
}
|
|
81
|
+
return newOpts
|
|
82
|
+
} else {
|
|
83
|
+
return opts
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Crudify (fastify, opts, next) {
|
|
88
|
+
const optsGet = addGetResponseUnitary(opts)
|
|
89
|
+
const optsGetAll = addGetResponseArray(opts)
|
|
90
|
+
const optsPost = addPostBody(optsGet)
|
|
91
|
+
|
|
92
|
+
fastify.get(`${opts.url}`, optsGetAll, async (req, reply) => {
|
|
93
|
+
try {
|
|
19
94
|
const filterObject = JSON.parse(req.query.filters ||= '{}')
|
|
20
95
|
for (const key in filterObject) {
|
|
21
96
|
if (typeof filterObject[key] === 'string') {
|
|
@@ -31,7 +106,7 @@ function Crudify (fastify, opts, next) {
|
|
|
31
106
|
}
|
|
32
107
|
})
|
|
33
108
|
|
|
34
|
-
fastify.get(`${opts.url}/:id`,
|
|
109
|
+
fastify.get(`${opts.url}/:id`, optsGet, async (req, reply) => {
|
|
35
110
|
try {
|
|
36
111
|
const response = await opts.Model.findById(req.params.id)
|
|
37
112
|
if (!response) {
|
|
@@ -48,7 +123,7 @@ function Crudify (fastify, opts, next) {
|
|
|
48
123
|
}
|
|
49
124
|
})
|
|
50
125
|
|
|
51
|
-
fastify.post(`${opts.url}`,
|
|
126
|
+
fastify.post(`${opts.url}`, optsPost, async (req, reply) => {
|
|
52
127
|
try {
|
|
53
128
|
await opts.Model.validate(req.body)
|
|
54
129
|
} catch (err) {
|
|
@@ -60,8 +135,8 @@ function Crudify (fastify, opts, next) {
|
|
|
60
135
|
throw new Error(err)
|
|
61
136
|
}
|
|
62
137
|
})
|
|
63
|
-
|
|
64
|
-
fastify.put(`${opts.url}/:id`,
|
|
138
|
+
|
|
139
|
+
fastify.put(`${opts.url}/:id`, optsGet, async (req, reply) => {
|
|
65
140
|
try {
|
|
66
141
|
const response = await opts.Model.findOneAndUpdate({ _id: req.params.id }, req.body, {
|
|
67
142
|
new: true,
|
package/jest-mongodb-config.js
CHANGED
package/package.json
CHANGED
package/test/crudify.test.js
CHANGED
|
@@ -10,10 +10,7 @@ let connection;
|
|
|
10
10
|
|
|
11
11
|
beforeAll(async () => {
|
|
12
12
|
app = fastify();
|
|
13
|
-
await connect(global.__MONGO_URI__
|
|
14
|
-
useNewUrlParser: true,
|
|
15
|
-
useUnifiedTopology: true,
|
|
16
|
-
});
|
|
13
|
+
await connect(global.__MONGO_URI__);
|
|
17
14
|
connection = mongoose.connection
|
|
18
15
|
const userSchema = new Schema({ name: String, age: Number, email: String });
|
|
19
16
|
userSchema.plugin(paginate);
|