Skip to content

Commit 2631c12

Browse files
committed
feat: add ommiting private fields feature
1 parent 79f27b2 commit 2631c12

File tree

6 files changed

+262
-52
lines changed

6 files changed

+262
-52
lines changed

docs/packages/node-mongo/overview.mdx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ interface ServiceOptions {
214214
|`escapeRegExp`|Escape `$regex` values to prevent special characters from being interpreted as patterns. |`false`|
215215
|`collectionOptions`|MongoDB [CollectionOptions](https://mongodb.github.io/node-mongodb-native/4.10/interfaces/CollectionOptions.html)|`{}`|
216216
|`collectionCreateOptions`|MongoDB [CreateCollectionOptions](https://mongodb.github.io/node-mongodb-native/4.10/interfaces/CreateCollectionOptions.html)|`{}`|
217+
|`privateFields`|Fields that should be omitted from the public response.|`[]`|
217218

218219
### `CreateConfig`
219220
Overrides `ServiceOptions` parameters for create operations.
@@ -222,6 +223,7 @@ Overrides `ServiceOptions` parameters for create operations.
222223
type CreateConfig = {
223224
validateSchema?: boolean,
224225
publishEvents?: boolean,
226+
mode?: 'public' | 'private',
225227
};
226228
```
227229

@@ -231,6 +233,8 @@ Overrides `ServiceOptions` parameters for read operations.
231233
```typescript
232234
type ReadConfig = {
233235
skipDeletedOnDocs?: boolean,
236+
populate?: PopulateOptions | PopulateOptions[],
237+
mode?: 'public' | 'private',
234238
};
235239
```
236240

@@ -242,6 +246,7 @@ type UpdateConfig = {
242246
skipDeletedOnDocs?: boolean,
243247
validateSchema?: boolean,
244248
publishEvents?: boolean,
249+
mode?: 'public' | 'private',
245250
};
246251
```
247252

@@ -281,19 +286,11 @@ Extending API for a single service.
281286
```typescript
282287
const service = db.createService<User>('users', {
283288
schemaValidator: (obj) => schema.parseAsync(obj),
289+
privateFields: ['passwordHash', 'signupToken', 'resetPasswordToken'],
284290
});
285291

286-
const privateFields = [
287-
'passwordHash',
288-
'signupToken',
289-
'resetPasswordToken',
290-
];
291-
292-
const getPublic = (user: User | null) => _.omit(user, privateFields);
293-
294292
export default Object.assign(service, {
295293
updateLastRequest,
296-
getPublic,
297294
});
298295
```
299296

packages/node-mongo/README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,15 +1046,11 @@ Extending API for a single service.
10461046
```typescript
10471047
const service = db.createService<User>("users", {
10481048
schemaValidator: (obj) => schema.parseAsync(obj),
1049+
privateFields: ['passwordHash', 'signupToken', 'resetPasswordToken'],
10491050
});
10501051

1051-
const privateFields = ["passwordHash", "signupToken", "resetPasswordToken"];
1052-
1053-
const getPublic = (user: User | null) => _.omit(user, privateFields);
1054-
10551052
export default Object.assign(service, {
10561053
updateLastRequest,
1057-
getPublic,
10581054
});
10591055
```
10601056

packages/node-mongo/src/service.ts

Lines changed: 61 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import {
3333
} from './types';
3434

3535
import logger from './utils/logger';
36-
import { addUpdatedOnField, generateId } from './utils/helpers';
36+
import { addUpdatedOnField, generateId, getPrivateFindOptions, omitPrivateFields } from './utils/helpers';
3737
import PopulateUtil from './utils/populate';
3838

3939
import { inMemoryPublisher } from './events/in-memory';
@@ -45,6 +45,7 @@ const defaultOptions: ServiceOptions = {
4545
addCreatedOnField: true,
4646
addUpdatedOnField: true,
4747
escapeRegExp: false,
48+
privateFields: [],
4849
};
4950

5051
const isDev = process.env.NODE_ENV === 'development';
@@ -239,17 +240,21 @@ class Service<T extends IDocument> {
239240
readConfig: ReadConfig = {},
240241
findOptions: FindOptions = {},
241242
): Promise<(U & PopulateTypes) | U | null> {
243+
const { mode = 'private', populate } = readConfig;
244+
242245
const collection = await this.getCollection<U>();
243246

244247
filter = this.handleReadOperations(filter, readConfig);
245248

246-
if (readConfig.populate) {
247-
const docs = await this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, findOptions);
249+
const privateFindOptions = getPrivateFindOptions({ mode, findOptions, privateFields: this.options.privateFields });
250+
251+
if (populate) {
252+
const docs = await this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, privateFindOptions);
248253

249254
return docs[0] || null;
250255
}
251256

252-
return collection.findOne<U>(filter, findOptions);
257+
return collection.findOne<U>(filter, privateFindOptions);
253258
}
254259

255260
// Method overloading for find
@@ -270,16 +275,19 @@ class Service<T extends IDocument> {
270275
readConfig: ReadConfig & { page?: number; perPage?: number } = {},
271276
findOptions: FindOptions = {},
272277
): Promise<FindResult<U & PopulateTypes> | FindResult<U>> {
278+
const { mode = 'private', populate, page, perPage } = readConfig;
279+
273280
const collection = await this.getCollection<U>();
274-
const { page, perPage } = readConfig;
275281
const hasPaging = !!page && !!perPage;
276282

277283
filter = this.handleReadOperations(filter, readConfig);
278284

285+
const privateFindOptions = getPrivateFindOptions({ mode, findOptions, privateFields: this.options.privateFields });
286+
279287
if (!hasPaging) {
280-
const results = readConfig.populate
281-
? await this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, findOptions)
282-
: await collection.find<U>(filter, findOptions).toArray();
288+
const results = populate
289+
? await this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, privateFindOptions)
290+
: await collection.find<U>(filter, privateFindOptions).toArray();
283291

284292
return {
285293
pagesCount: 1,
@@ -288,13 +296,13 @@ class Service<T extends IDocument> {
288296
};
289297
}
290298

291-
findOptions.skip = (page - 1) * perPage;
292-
findOptions.limit = perPage;
299+
privateFindOptions.skip = (page - 1) * perPage;
300+
privateFindOptions.limit = perPage;
293301

294302
const [results, count] = await Promise.all([
295-
readConfig.populate
296-
? this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, findOptions)
297-
: collection.find<U>(filter, findOptions).toArray(),
303+
populate
304+
? this.populateAggregate<U, PopulateTypes>(collection, filter, readConfig, privateFindOptions)
305+
: collection.find<U>(filter, privateFindOptions).toArray(),
298306
collection.countDocuments(filter),
299307
]);
300308

@@ -347,12 +355,13 @@ class Service<T extends IDocument> {
347355
createConfig: CreateConfig = {},
348356
insertOneOptions: InsertOneOptions = {},
349357
): Promise<U> => {
358+
const { mode = 'private', publishEvents } = createConfig;
350359
const collection = await this.getCollection<U>();
351360

352361
const validEntity = await this.validateCreateOperation<U>(object, createConfig);
353362

354-
const shouldPublishEvents = typeof createConfig.publishEvents === 'boolean'
355-
? createConfig.publishEvents
363+
const shouldPublishEvents = typeof publishEvents === 'boolean'
364+
? publishEvents
356365
: this.options.publishEvents;
357366

358367
if (shouldPublishEvents) {
@@ -376,22 +385,28 @@ class Service<T extends IDocument> {
376385
await collection.insertOne(validEntity as OptionalUnlessRequiredId<U>, insertOneOptions);
377386
}
378387

379-
return validEntity;
388+
if (mode === 'public') {
389+
return validEntity;
390+
}
391+
392+
return omitPrivateFields<U>(validEntity, this.options.privateFields);
380393
};
381394

382395
insertMany = async <U extends T = T>(
383396
objects: Partial<U>[],
384397
createConfig: CreateConfig = {},
385398
bulkWriteOptions: BulkWriteOptions = {},
386-
): Promise<U[]> => {
399+
): Promise<Array<U>> => {
400+
const { mode = 'private', publishEvents } = createConfig;
401+
387402
const collection = await this.getCollection<U>();
388403

389404
const validEntities = await Promise.all(objects.map(
390405
(o) => this.validateCreateOperation<U>(o, createConfig),
391406
));
392407

393-
const shouldPublishEvents = typeof createConfig.publishEvents === 'boolean'
394-
? createConfig.publishEvents
408+
const shouldPublishEvents = typeof publishEvents === 'boolean'
409+
? publishEvents
395410
: this.options.publishEvents;
396411

397412
if (shouldPublishEvents) {
@@ -415,7 +430,11 @@ class Service<T extends IDocument> {
415430
await collection.insertMany(validEntities as OptionalUnlessRequiredId<U>[], bulkWriteOptions);
416431
}
417432

418-
return validEntities;
433+
if (mode === 'public') {
434+
return validEntities;
435+
}
436+
437+
return validEntities.map((doc) => omitPrivateFields<U>(doc, this.options.privateFields));
419438
};
420439

421440
replaceOne = async (
@@ -455,6 +474,8 @@ class Service<T extends IDocument> {
455474
updateConfig: UpdateConfig = {},
456475
updateOptions: UpdateOptions = {},
457476
): Promise<U | null> {
477+
const { mode = 'private', validateSchema, publishEvents } = updateConfig;
478+
458479
const collection = await this.getCollection<U>();
459480

460481
filter = this.handleReadOperations(filter, updateConfig);
@@ -515,16 +536,16 @@ class Service<T extends IDocument> {
515536
updateFilter = _.merge(updateFilter, { $set: { updatedOn: updatedOnDate } });
516537
}
517538

518-
const shouldValidateSchema = typeof updateConfig.validateSchema === 'boolean'
519-
? updateConfig.validateSchema
539+
const shouldValidateSchema = typeof validateSchema === 'boolean'
540+
? validateSchema
520541
: Boolean(this.options.schemaValidator);
521542

522543
if (shouldValidateSchema) {
523544
await this.validateSchema(newDoc);
524545
}
525546

526-
const shouldPublishEvents = typeof updateConfig.publishEvents === 'boolean'
527-
? updateConfig.publishEvents
547+
const shouldPublishEvents = typeof publishEvents === 'boolean'
548+
? publishEvents
528549
: this.options.publishEvents;
529550

530551
if (shouldPublishEvents) {
@@ -556,7 +577,11 @@ class Service<T extends IDocument> {
556577
);
557578
}
558579

559-
return newDoc;
580+
if (mode === 'public') {
581+
return newDoc;
582+
}
583+
584+
return omitPrivateFields<U>(newDoc, this.options.privateFields);
560585
}
561586

562587
updateMany<U extends T = T>(
@@ -579,6 +604,8 @@ class Service<T extends IDocument> {
579604
updateConfig: UpdateConfig = {},
580605
updateOptions: UpdateOptions = {},
581606
): Promise<U[]> {
607+
const { mode = 'private', validateSchema, publishEvents } = updateConfig;
608+
582609
const collection = await this.getCollection<U>();
583610

584611
filter = this.handleReadOperations(filter, updateConfig);
@@ -654,8 +681,8 @@ class Service<T extends IDocument> {
654681
});
655682
}
656683

657-
const shouldValidateSchema = typeof updateConfig.validateSchema === 'boolean'
658-
? updateConfig.validateSchema
684+
const shouldValidateSchema = typeof validateSchema === 'boolean'
685+
? validateSchema
659686
: Boolean(this.options.schemaValidator);
660687

661688
if (shouldValidateSchema) {
@@ -676,8 +703,8 @@ class Service<T extends IDocument> {
676703
},
677704
);
678705

679-
const shouldPublishEvents = typeof updateConfig.publishEvents === 'boolean'
680-
? updateConfig.publishEvents
706+
const shouldPublishEvents = typeof publishEvents === 'boolean'
707+
? publishEvents
681708
: this.options.publishEvents;
682709

683710
if (shouldPublishEvents) {
@@ -701,7 +728,11 @@ class Service<T extends IDocument> {
701728
await collection.bulkWrite(bulkWriteQuery, updateOptions);
702729
}
703730

704-
return updated.map((u) => u?.doc) as U[];
731+
if (mode === 'public') {
732+
return updated.map((u) => u?.doc) as U[];
733+
}
734+
735+
return updated.map((u) => omitPrivateFields<U>(u?.doc as U, this.options.privateFields)) as U[];
705736
}
706737

707738
deleteOne = async <U extends T = T>(

0 commit comments

Comments
 (0)