@point-hub/papi 0.1.5 → 0.1.6
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 +4 -3
- package/src/app.spec.ts +8 -0
- package/src/app.ts +13 -0
- package/src/console/commands/make-command/index.command.ts +52 -0
- package/src/console/commands/make-command/index.spec.ts +58 -0
- package/src/console/commands/make-middleware/index.command.ts +86 -0
- package/src/console/commands/make-middleware/index.spec.ts +73 -0
- package/src/console/index.spec.ts +14 -0
- package/src/console/index.ts +23 -0
- package/src/database/connection.ts +78 -0
- package/src/database/mongodb/connection.ts +339 -0
- package/src/database/mongodb/mongodb-error-handler.ts +63 -0
- package/src/database/mongodb/mongodb-helper.ts +109 -0
- package/src/database/mongodb/mongodb-querystring.spec.ts +80 -0
- package/src/database/mongodb/mongodb-querystring.ts +143 -0
- package/src/index.ts +42 -0
- package/src/server.spec.ts +68 -0
- package/src/server.ts +49 -0
- package/src/types/controller.d.ts +18 -0
- package/src/types/database.d.ts +104 -0
- package/src/types/use-case.d.ts +3 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type {
|
|
3
|
+
AggregateOptions,
|
|
4
|
+
BulkWriteOptions,
|
|
5
|
+
ClientSession,
|
|
6
|
+
Collection,
|
|
7
|
+
CollectionOptions,
|
|
8
|
+
CreateIndexesOptions,
|
|
9
|
+
Db,
|
|
10
|
+
DbOptions,
|
|
11
|
+
DeleteOptions,
|
|
12
|
+
FindOptions,
|
|
13
|
+
IndexSpecification,
|
|
14
|
+
InsertOneOptions,
|
|
15
|
+
MongoClientOptions,
|
|
16
|
+
UpdateOptions,
|
|
17
|
+
} from 'mongodb'
|
|
18
|
+
import { MongoClient, ObjectId } from 'mongodb'
|
|
19
|
+
|
|
20
|
+
import { MongoDBHelper } from './mongodb-helper'
|
|
21
|
+
import Querystring from './mongodb-querystring'
|
|
22
|
+
|
|
23
|
+
export class MongoDBConnection implements IDatabase {
|
|
24
|
+
public client: MongoClient
|
|
25
|
+
public _database: Db | undefined
|
|
26
|
+
public _collection: Collection | undefined
|
|
27
|
+
public session: ClientSession | undefined
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
connectionString: string,
|
|
31
|
+
public databaseName: string,
|
|
32
|
+
) {
|
|
33
|
+
const options: MongoClientOptions = {}
|
|
34
|
+
|
|
35
|
+
this.client = new MongoClient(connectionString, options)
|
|
36
|
+
this.database(databaseName)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async open() {
|
|
40
|
+
await this.client.connect()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async close() {
|
|
44
|
+
await this.client.close()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public database(name: string, options?: DbOptions) {
|
|
48
|
+
this._database = this.client.db(name, options)
|
|
49
|
+
return this
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public collection(name: string, options?: CollectionOptions) {
|
|
53
|
+
if (!this._database) {
|
|
54
|
+
throw new Error('Database not found')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this._collection = this._database.collection(name, options)
|
|
58
|
+
return this
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public async listCollections(): Promise<{ name: string }[]> {
|
|
62
|
+
if (!this._database) {
|
|
63
|
+
throw new Error('Database not found')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return await this._database.listCollections().toArray()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async createIndex(name: string, spec: IndexSpecification, options: CreateIndexesOptions): Promise<void> {
|
|
70
|
+
if (!this._database) {
|
|
71
|
+
throw new Error('Database not found')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await this._database.createIndex(name, spec, options)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public async updateSchema(name: string, schema: unknown): Promise<void> {
|
|
78
|
+
if (!this._database) {
|
|
79
|
+
throw new Error('Database not found')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await this._database.command({
|
|
83
|
+
collMod: name,
|
|
84
|
+
validator: {
|
|
85
|
+
$jsonSchema: schema,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async createCollection(name: string, options: any): Promise<void> {
|
|
91
|
+
if (!this._database) {
|
|
92
|
+
throw new Error('Database not found')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await this._database.createCollection(name, options)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public async dropCollection(name: string, options: any): Promise<void> {
|
|
99
|
+
if (!this._database) {
|
|
100
|
+
throw new Error('Database not found')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await this._database.dropCollection(name, options)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public startSession() {
|
|
107
|
+
this.session = this.client.startSession()
|
|
108
|
+
return this.session
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public async endSession() {
|
|
112
|
+
await this.session?.endSession()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public startTransaction() {
|
|
116
|
+
this.session?.startTransaction()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async commitTransaction() {
|
|
120
|
+
await this.session?.commitTransaction()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public async abortTransaction() {
|
|
124
|
+
await this.session?.abortTransaction()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public async create(document: IDocument, options?: unknown): Promise<ICreateOutput> {
|
|
128
|
+
if (!this._collection) {
|
|
129
|
+
throw new Error('Collection not found')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const createOptions = options as InsertOneOptions
|
|
133
|
+
|
|
134
|
+
const response = await this._collection.insertOne(document, createOptions)
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
insertedId: response.insertedId.toString(),
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public async createMany(documents: IDocument[], options?: unknown): Promise<ICreateManyOutput> {
|
|
142
|
+
if (!this._collection) {
|
|
143
|
+
throw new Error('Collection not found')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const createManyOptions = options as BulkWriteOptions
|
|
147
|
+
|
|
148
|
+
const response = await this._collection.insertMany(documents, createManyOptions)
|
|
149
|
+
|
|
150
|
+
// convert array of object to array of string
|
|
151
|
+
const insertedIds: string[] = []
|
|
152
|
+
Object.values(response.insertedIds).forEach((val) => {
|
|
153
|
+
insertedIds.push(val.toString())
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
insertedIds: insertedIds,
|
|
158
|
+
insertedCount: response.insertedCount,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public async retrieveAll(query: IQuery, options?: any): Promise<IRetrieveAllOutput> {
|
|
163
|
+
if (!this._collection) {
|
|
164
|
+
throw new Error('Collection not found')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const retrieveOptions = options as FindOptions
|
|
168
|
+
|
|
169
|
+
const cursor = this._collection
|
|
170
|
+
.find(MongoDBHelper.stringToObjectId(query.filter ?? {}), retrieveOptions)
|
|
171
|
+
.limit(Querystring.limit(query.pageSize))
|
|
172
|
+
.skip(Querystring.skip(Querystring.page(query.page), Querystring.limit(query.pageSize)))
|
|
173
|
+
|
|
174
|
+
if (query.sort && Querystring.sort(query.sort)) {
|
|
175
|
+
cursor.sort(Querystring.sort(query.sort))
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Querystring.fields(query.fields, query.excludeFields)) {
|
|
179
|
+
cursor.project(Querystring.fields(query.fields, query.excludeFields))
|
|
180
|
+
}
|
|
181
|
+
const result = await cursor.toArray()
|
|
182
|
+
|
|
183
|
+
const totalDocument = await this._collection.countDocuments(query.filter ?? {}, retrieveOptions)
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
data: MongoDBHelper.objectIdToString(result) as unknown[] as IRetrieveOutput[],
|
|
187
|
+
pagination: {
|
|
188
|
+
page: Querystring.page(query.page),
|
|
189
|
+
pageCount: Math.ceil(totalDocument / Querystring.limit(query.pageSize)),
|
|
190
|
+
pageSize: Querystring.limit(query.pageSize),
|
|
191
|
+
totalDocument,
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public async retrieve(_id: string, options?: any): Promise<IRetrieveOutput> {
|
|
197
|
+
if (!this._collection) {
|
|
198
|
+
throw new Error('Collection not found')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const retrieveOptions = options as FindOptions
|
|
202
|
+
|
|
203
|
+
const result = await this._collection.findOne(
|
|
204
|
+
{
|
|
205
|
+
_id: new ObjectId(_id),
|
|
206
|
+
},
|
|
207
|
+
retrieveOptions,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return MongoDBHelper.objectIdToString(result)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public async update(_id: string, document: IDocument, options?: any): Promise<IUpdateOutput> {
|
|
214
|
+
if (!this._collection) {
|
|
215
|
+
throw new Error('Collection not found')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const updateOptions = options as UpdateOptions
|
|
219
|
+
|
|
220
|
+
const result = await this._collection.updateOne(
|
|
221
|
+
{ _id: new ObjectId(_id) },
|
|
222
|
+
{ $set: MongoDBHelper.stringToObjectId(document) },
|
|
223
|
+
updateOptions,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
modifiedCount: result.modifiedCount,
|
|
228
|
+
matchedCount: result.matchedCount,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public async updateMany(filter: IDocument[], document: IDocument[], options?: any): Promise<IUpdateManyOutput> {
|
|
233
|
+
if (!this._collection) {
|
|
234
|
+
throw new Error('Collection not found')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const updateManyOptions = options as UpdateOptions
|
|
238
|
+
|
|
239
|
+
const result = await this._collection.updateMany(
|
|
240
|
+
filter,
|
|
241
|
+
{ $set: MongoDBHelper.stringToObjectId(document) },
|
|
242
|
+
updateManyOptions,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
matchedCount: result.matchedCount,
|
|
247
|
+
modifiedCount: result.modifiedCount,
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public async delete(_id: string, options?: any): Promise<IDeleteOutput> {
|
|
252
|
+
if (!this._collection) {
|
|
253
|
+
throw new Error('Collection not found')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const deleteOptions = options as DeleteOptions
|
|
257
|
+
|
|
258
|
+
const result = await this._collection.deleteOne(
|
|
259
|
+
{
|
|
260
|
+
_id: new ObjectId(_id),
|
|
261
|
+
},
|
|
262
|
+
deleteOptions,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return { deletedCount: result.deletedCount }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public async deleteMany(_ids: string[], options?: any): Promise<IDeleteManyOutput> {
|
|
269
|
+
if (!this._collection) {
|
|
270
|
+
throw new Error('Collection not found')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const deleteOptions = options as DeleteOptions
|
|
274
|
+
|
|
275
|
+
const result = await this._collection.deleteMany(
|
|
276
|
+
{
|
|
277
|
+
_id: {
|
|
278
|
+
$in: MongoDBHelper.stringToObjectId(_ids),
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
deleteOptions,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return { deletedCount: result.deletedCount }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
public async deleteAll(options?: any): Promise<IDeleteManyOutput> {
|
|
288
|
+
if (!this._collection) {
|
|
289
|
+
throw new Error('Collection not found')
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const deleteOptions = options as DeleteOptions
|
|
293
|
+
|
|
294
|
+
const result = await this._collection.deleteMany({}, deleteOptions)
|
|
295
|
+
|
|
296
|
+
return { deletedCount: result.deletedCount }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
public async aggregate(pipeline: IPipeline[], query: IQuery, options?: any): Promise<IAggregateOutput> {
|
|
300
|
+
if (!this._collection) {
|
|
301
|
+
throw new Error('Collection not found')
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const aggregateOptions = options as AggregateOptions
|
|
305
|
+
|
|
306
|
+
const cursor = this._collection.aggregate(
|
|
307
|
+
[
|
|
308
|
+
...pipeline,
|
|
309
|
+
{ $skip: (Querystring.page(query.page) - 1) * Querystring.limit(query.pageSize) },
|
|
310
|
+
{ $limit: Querystring.limit(query.pageSize) },
|
|
311
|
+
],
|
|
312
|
+
aggregateOptions,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
if (query.sort && Querystring.sort(query.sort)) {
|
|
316
|
+
cursor.sort(Querystring.sort(query.sort))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (Querystring.fields(query.fields, query.excludeFields)) {
|
|
320
|
+
cursor.project(Querystring.fields(query.fields, query.excludeFields))
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const result = await cursor.toArray()
|
|
324
|
+
|
|
325
|
+
const cursorPagination = this._collection.aggregate([...pipeline, { $count: 'totalDocument' }], aggregateOptions)
|
|
326
|
+
const resultPagination = await cursorPagination.toArray()
|
|
327
|
+
|
|
328
|
+
const totalDocument = resultPagination.length ? resultPagination[0].totalDocument : 0
|
|
329
|
+
return {
|
|
330
|
+
data: MongoDBHelper.objectIdToString(result) as IRetrieveOutput[],
|
|
331
|
+
pagination: {
|
|
332
|
+
page: Querystring.page(query.page),
|
|
333
|
+
pageCount: Math.ceil(totalDocument / Querystring.limit(query.pageSize)),
|
|
334
|
+
pageSize: Querystring.limit(query.pageSize),
|
|
335
|
+
totalDocument,
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { BaseError, find, type IError, type IHttpStatus } from '@point-hub/express-error-handler'
|
|
3
|
+
import { MongoServerError } from 'mongodb'
|
|
4
|
+
|
|
5
|
+
export function handleSchemaValidation(err: MongoServerError, error: IError) {
|
|
6
|
+
// handle schema validation error
|
|
7
|
+
error.errors = {} as any
|
|
8
|
+
const errorMessage = err.errInfo?.details.schemaRulesNotSatisfied[0].propertiesNotSatisfied
|
|
9
|
+
errorMessage.forEach((element: any) => {
|
|
10
|
+
const obj: any = {}
|
|
11
|
+
obj[element.propertyName] = [element.details[0].reason]
|
|
12
|
+
error.errors = obj
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handleUniqueValidation(err: MongoServerError, error: IError) {
|
|
17
|
+
// handle unique validation
|
|
18
|
+
if (Object.keys(err.keyPattern).length === 1) {
|
|
19
|
+
error.errors = {
|
|
20
|
+
[Object.keys(err.keyPattern)[0]]: [`The ${Object.keys(err.keyPattern)[0]} is exists.`],
|
|
21
|
+
}
|
|
22
|
+
} else {
|
|
23
|
+
// get keys
|
|
24
|
+
const keys = Object.keys(err.keyPattern).reduce((keys: string, key, index) => {
|
|
25
|
+
if (index === 0) {
|
|
26
|
+
keys += `'`
|
|
27
|
+
}
|
|
28
|
+
keys += `${key.toString()}`
|
|
29
|
+
if (index === Object.keys(err.keyPattern).length - 1) {
|
|
30
|
+
keys += `'`
|
|
31
|
+
} else {
|
|
32
|
+
keys += `, `
|
|
33
|
+
}
|
|
34
|
+
return keys
|
|
35
|
+
}, '')
|
|
36
|
+
|
|
37
|
+
// generate error object
|
|
38
|
+
const obj = Object.keys(err.keyPattern).reduce((obj: any, key) => {
|
|
39
|
+
obj[key] = [`The combination of ${keys.toString()} is exists.`]
|
|
40
|
+
return obj
|
|
41
|
+
}, {})
|
|
42
|
+
error.errors = obj
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class MongoErrorHandler extends BaseError {
|
|
47
|
+
constructor(err: MongoServerError) {
|
|
48
|
+
const error: IError = find(400) as IHttpStatus
|
|
49
|
+
if (err.code === 121) {
|
|
50
|
+
handleSchemaValidation(err, error)
|
|
51
|
+
} else if (err.code === 11000) {
|
|
52
|
+
handleUniqueValidation(err, error)
|
|
53
|
+
}
|
|
54
|
+
super(error)
|
|
55
|
+
Object.setPrototypeOf(this, new.target.prototype)
|
|
56
|
+
}
|
|
57
|
+
get isOperational() {
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
override get name() {
|
|
61
|
+
return 'MongoErrorHandler'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { isValid } from 'date-fns'
|
|
2
|
+
import { ObjectId } from 'mongodb'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* https://www.mongodb.com/docs/drivers/node/current/fundamentals/indexes/
|
|
6
|
+
* https://www.mongodb.com/docs/manual/reference/collation/
|
|
7
|
+
* https://www.mongodb.com/docs/manual/core/index-sparse/
|
|
8
|
+
* https://www.mongodb.com/docs/manual/core/index-partial/
|
|
9
|
+
*/
|
|
10
|
+
export class MongoDBHelper {
|
|
11
|
+
private db
|
|
12
|
+
|
|
13
|
+
constructor(db: IDatabase) {
|
|
14
|
+
this.db = db
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create unique column
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* create unique attribute "name"
|
|
22
|
+
* createUnique(collection, {
|
|
23
|
+
* name: -1,
|
|
24
|
+
* })
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* create unique attribute for multiple column "first_name" and "last_name"
|
|
28
|
+
* createUnique(collection, {
|
|
29
|
+
* firstName: -1,
|
|
30
|
+
* lastName: -1,
|
|
31
|
+
* })
|
|
32
|
+
*/
|
|
33
|
+
public async createUnique(collection: string, properties: object) {
|
|
34
|
+
await this.db.createIndex(collection, properties, {
|
|
35
|
+
unique: true,
|
|
36
|
+
collation: {
|
|
37
|
+
locale: 'en',
|
|
38
|
+
strength: 2,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create unique if column is exists
|
|
45
|
+
*/
|
|
46
|
+
public async createUniqueIfNotNull(collection: string, properties: object) {
|
|
47
|
+
await this.db.createIndex(collection, properties, {
|
|
48
|
+
unique: true,
|
|
49
|
+
sparse: true,
|
|
50
|
+
collation: {
|
|
51
|
+
locale: 'en',
|
|
52
|
+
strength: 2,
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public async isExists(name: string) {
|
|
58
|
+
const collections = (await this.db.listCollections()) as []
|
|
59
|
+
return collections.some(function (collection: { name: string }) {
|
|
60
|
+
return collection.name === name
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
|
+
public static stringToObjectId(val: any): any {
|
|
66
|
+
if (val == null) return null
|
|
67
|
+
if (Array.isArray(val)) {
|
|
68
|
+
return val.map((item) => {
|
|
69
|
+
return MongoDBHelper.stringToObjectId(item)
|
|
70
|
+
})
|
|
71
|
+
} else if (typeof val === 'object' && !isValid(val)) {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
return Object.keys(val).reduce((obj: any, key) => {
|
|
74
|
+
const propVal = MongoDBHelper.stringToObjectId(val[key])
|
|
75
|
+
obj[key] = propVal
|
|
76
|
+
return obj
|
|
77
|
+
}, {})
|
|
78
|
+
} else if (typeof val === 'string' && ObjectId.isValid(val) && val === new ObjectId(val).toString()) {
|
|
79
|
+
return new ObjectId(val)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return val
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
public static objectIdToString(val: any): any {
|
|
87
|
+
if (val == null) return null
|
|
88
|
+
if (Array.isArray(val)) {
|
|
89
|
+
return val.map((item) => {
|
|
90
|
+
return MongoDBHelper.objectIdToString(item)
|
|
91
|
+
})
|
|
92
|
+
} else if (typeof val === 'object' && ObjectId.isValid(val)) {
|
|
93
|
+
return val.toString()
|
|
94
|
+
} else if (typeof val === 'object' && !isValid(val)) {
|
|
95
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
96
|
+
return Object.keys(val).reduce((obj: any, key) => {
|
|
97
|
+
if (ObjectId.isValid(val) || isValid(val)) {
|
|
98
|
+
return val.toString()
|
|
99
|
+
} else {
|
|
100
|
+
const propVal = MongoDBHelper.objectIdToString(val[key])
|
|
101
|
+
obj[key] = propVal
|
|
102
|
+
return obj
|
|
103
|
+
}
|
|
104
|
+
}, {})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return val
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
convertArrayToObject,
|
|
5
|
+
convertStringToArray,
|
|
6
|
+
fields,
|
|
7
|
+
filterExludeFields,
|
|
8
|
+
limit,
|
|
9
|
+
page,
|
|
10
|
+
skip,
|
|
11
|
+
sort,
|
|
12
|
+
} from './mongodb-querystring'
|
|
13
|
+
|
|
14
|
+
describe('field', () => {
|
|
15
|
+
it('convert string to array', async () => {
|
|
16
|
+
expect(convertStringToArray('name, password')).toStrictEqual(['name', 'password'])
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('convert array to mongodb field object', async () => {
|
|
20
|
+
expect(convertArrayToObject(['name', 'password'])).toStrictEqual({
|
|
21
|
+
name: 1,
|
|
22
|
+
password: 1,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('add excluded fields to the object', async () => {
|
|
27
|
+
const obj = { name: 1, password: 1 }
|
|
28
|
+
const excluded = ['password']
|
|
29
|
+
const result = {
|
|
30
|
+
...obj,
|
|
31
|
+
...filterExludeFields(obj, excluded),
|
|
32
|
+
}
|
|
33
|
+
expect(result).toStrictEqual({
|
|
34
|
+
name: 1,
|
|
35
|
+
password: 0,
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('filter fields', async () => {
|
|
40
|
+
const result = fields('', ['password'])
|
|
41
|
+
expect(result).toStrictEqual({
|
|
42
|
+
password: 0,
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('page', () => {
|
|
48
|
+
it('convert page string to number', async () => {
|
|
49
|
+
expect(page('1')).toStrictEqual(1)
|
|
50
|
+
})
|
|
51
|
+
it('default page should be 1', async () => {
|
|
52
|
+
expect(page()).toStrictEqual(1)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('limit', () => {
|
|
57
|
+
it('convert limit string to number', async () => {
|
|
58
|
+
expect(limit('1')).toStrictEqual(1)
|
|
59
|
+
})
|
|
60
|
+
it('default limit should be 10', async () => {
|
|
61
|
+
expect(limit()).toStrictEqual(10)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('skip', () => {
|
|
66
|
+
it('should skip number of data from page', async () => {
|
|
67
|
+
expect(skip(1, 10)).toStrictEqual(0)
|
|
68
|
+
expect(skip(2, 10)).toStrictEqual(10)
|
|
69
|
+
expect(skip(2, 50)).toStrictEqual(50)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('sort', () => {
|
|
74
|
+
it('convert string to mongodb sort object', async () => {
|
|
75
|
+
expect(sort('name,-address')).toStrictEqual({
|
|
76
|
+
name: 1,
|
|
77
|
+
address: -1,
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
})
|