Skip to content

Commit

Permalink
feat(mongodb): Use new filter on typegoose query service
Browse files Browse the repository at this point in the history
  • Loading branch information
marian2js authored and doug-martin committed Oct 16, 2020
1 parent adc4326 commit de34e92
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,26 @@ describe('TypegooseQueryService', () => {
const found = await queryService.findById(new mongoose.Types.ObjectId().toString());
expect(found).toBeUndefined();
});

describe('with filter', () => {
it('should return an entity if all filters match', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
const found = await queryService.findById(entity.id, {
filter: { stringType: { eq: entity.stringType } },
});
expect(found).toEqual(entity.getOutputData());
});

it('should return an undefined if an entity with the pk and filter is not found', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
const found = await queryService.findById(entity.id, {
filter: { stringType: { eq: TEST_ENTITIES[1].stringType } },
});
expect(found).toBeUndefined();
});
});
});

describe('#getById', () => {
Expand All @@ -161,6 +181,27 @@ describe('TypegooseQueryService', () => {
const queryService = moduleRef.get(TestEntityService);
return expect(queryService.getById(badId)).rejects.toThrow(`Unable to find TestEntity with id: ${badId}`);
});

describe('with filter', () => {
it('should return an entity if all filters match', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
const found = await queryService.getById(entity.id, {
filter: { stringType: { eq: entity.stringType } },
});
expect(found).toEqual(entity.getOutputData());
});

it('should return an undefined if an entitity with the pk and filter is not found', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
return expect(
queryService.getById(entity.id, {
filter: { stringType: { eq: TEST_ENTITIES[1].stringType } },
}),
).rejects.toThrow(`Unable to find TestEntity with id: ${entity.id}`);
});
});
});

describe('#createMany', () => {
Expand Down Expand Up @@ -233,6 +274,27 @@ describe('TypegooseQueryService', () => {
const queryService = moduleRef.get(TestEntityService);
return expect(queryService.deleteOne(badId)).rejects.toThrow(`Unable to find TestEntity with id: ${badId}`);
});

describe('with filter', () => {
it('should delete the entity if all filters match', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
const deleted = await queryService.deleteOne(entity.id, {
filter: { stringType: { eq: entity.stringType } },
});
expect(deleted).toEqual(TEST_ENTITIES[0].getOutputData());
});

it('should return throw an error if unable to find ', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
return expect(
queryService.deleteOne(entity.id, {
filter: { stringType: { eq: TEST_ENTITIES[1].stringType } },
}),
).rejects.toThrow(`Unable to find TestEntity with id: ${entity.id}`);
});
});
});

describe('#updateMany', () => {
Expand Down Expand Up @@ -278,6 +340,31 @@ describe('TypegooseQueryService', () => {
`Unable to find TestEntity with id: ${badId}`,
);
});

describe('with filter', () => {
it('should update the entity if all filters match', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
const updated = await queryService.updateOne(
entity.id,
{ stringType: 'updated' },
{ filter: { stringType: { eq: entity.stringType } } },
);
expect(updated).toEqual({ ...entity.getOutputData(), stringType: 'updated' });
});

it('should throw an error if unable to find the entity', async () => {
const entity = TEST_ENTITIES[0];
const queryService = moduleRef.get(TestEntityService);
return expect(
queryService.updateOne(
entity.id,
{ stringType: 'updated' },
{ filter: { stringType: { eq: TEST_ENTITIES[1].stringType } } },
),
).rejects.toThrow(`Unable to find TestEntity with id: ${entity.id}`);
});
});
});

describe('#findRelation', () => {
Expand Down Expand Up @@ -306,6 +393,21 @@ describe('TypegooseQueryService', () => {

expect(queryResult.values().next().value).toBeUndefined();
});

it('should apply the filter option', async () => {
const entity = TEST_ENTITIES[10];
const queryService = moduleRef.get(TestEntityService);

const queryResult1 = await queryService.findRelation(TestReference, 'testReference', [entity], {
filter: { name: { eq: TEST_REFERENCES[0].name } },
});
expect(queryResult1.values().next().value).toEqual(TEST_REFERENCES[0].getOutputData());

const queryResult2 = await queryService.findRelation(TestReference, 'testReference', [entity], {
filter: { name: { eq: TEST_REFERENCES[1].name } },
});
expect(queryResult2.values().next().value).toBeUndefined();
});
});

describe('#queryRelations', () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/query-typegoose/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
},
"dependencies": {
"@nestjs-query/core": "0.19.0",
"lodash.escaperegexp": "^4.1.2"
"lodash.escaperegexp": "^4.1.2",
"lodash.merge": "^4.6.2"
},
"peerDependencies": {
"@nestjs/common": "^7.0.0",
Expand All @@ -33,6 +34,7 @@
"@nestjs/testing": "7.4.2",
"@typegoose/typegoose": "^7.3.3",
"@types/lodash.escaperegexp": "^4.1.6",
"@types/lodash.merge": "^4.6.6",
"@types/mongodb": "^3.5.26",
"@types/mongoose": "^5.7.36",
"class-transformer": "0.3.1",
Expand Down
60 changes: 44 additions & 16 deletions packages/query-typegoose/src/services/typegoose-query.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@ import {
Class,
DeepPartial,
DeleteManyResponse,
DeleteOneOptions,
Filter,
FindByIdOptions,
FindRelationOptions,
GetByIdOptions,
Query,
QueryService,
UpdateManyResponse,
UpdateOneOptions,
} from '@nestjs-query/core';
import { NotFoundException } from '@nestjs/common';
import { DocumentToObjectOptions, FilterQuery, UpdateQuery } from 'mongoose';
import { ReturnModelType } from '@typegoose/typegoose';
import escapeRegExp from 'lodash.escaperegexp';
import merge from 'lodash.merge';

export interface TypegooseQueryServiceOpts {
documentToObjectOptions?: DocumentToObjectOptions;
Expand Down Expand Up @@ -54,8 +60,8 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
this.documentToObjectOptions = opts?.documentToObjectOptions || { virtuals: true };
}

protected buildExpression(filter: Filter<Entity>): FilterQuery<new () => Entity> {
return Object.entries(filter).reduce((prev: FilterQuery<new () => Entity>, [key, value]) => {
protected buildExpression<T>(filter: Filter<T>): FilterQuery<new () => T> {
return Object.entries(filter).reduce((prev: FilterQuery<new () => T>, [key, value]) => {
if (!value) {
return prev;
}
Expand Down Expand Up @@ -102,6 +108,13 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
return key === 'id' ? '_id' : key;
}

private mergeFilterWithId<T>(id: unknown, filter?: Filter<T>): FilterQuery<new () => T> {
return merge({
...this.buildExpression(filter || {}),
[this.getSchemaKey('id')]: id,
}) as FilterQuery<new () => T>;
}

/**
* Query for multiple entities, using a Query from `@nestjs-query/core`.
*
Expand Down Expand Up @@ -146,9 +159,10 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
* const todoItem = await this.service.findById(1);
* ```
* @param id - The id of the record to find.
* @param opts - Additional options
*/
async findById(id: string): Promise<Entity | undefined> {
const doc = await this.Model.findById(id);
async findById(id: string, opts?: FindByIdOptions<Entity>): Promise<Entity | undefined> {
const doc = await this.Model.findOne(this.mergeFilterWithId(id, opts?.filter));
return doc?.toObject(this.documentToObjectOptions) as Entity;
}

Expand All @@ -164,9 +178,10 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
* }
* ```
* @param id - The id of the record to find.
* @param opts - Additional options
*/
async getById(id: string): Promise<Entity> {
const entity = await this.findById(id);
async getById(id: string, opts?: GetByIdOptions<Entity>): Promise<Entity> {
const entity = await this.findById(id, opts);
if (!entity) {
throw new NotFoundException(`Unable to find ${this.Model.modelName} with id: ${id}`);
}
Expand Down Expand Up @@ -213,10 +228,19 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
* ```
* @param id - The `id` of the record.
* @param update - A `Partial` of the entity with fields to update.
* @param opts - Additional options
*/
async updateOne<U extends DeepPartial<Entity>>(id: string, update: U): Promise<Entity> {
async updateOne<U extends DeepPartial<Entity>>(
id: string,
update: U,
opts?: UpdateOneOptions<Entity>,
): Promise<Entity> {
this.ensureIdIsNotPresent(update);
const doc = await this.Model.findByIdAndUpdate(id, update as UpdateQuery<new () => Entity>, { new: true });
const doc = await this.Model.findOneAndUpdate(
this.mergeFilterWithId(id, opts?.filter),
update as UpdateQuery<new () => Entity>,
{ new: true },
);
if (doc) {
return doc.toObject(this.documentToObjectOptions) as Entity;
}
Expand Down Expand Up @@ -255,9 +279,10 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
* ```
*
* @param id - The `id` of the entity to delete.
* @param opts - Additional filter to use when finding the entity to delete.
*/
async deleteOne(id: string | number): Promise<Entity> {
const doc = await this.Model.findByIdAndDelete(id);
async deleteOne(id: string, opts?: DeleteOneOptions<Entity>): Promise<Entity> {
const doc = await this.Model.findOneAndDelete(this.mergeFilterWithId(id, opts?.filter));
if (doc) {
return doc.toObject(this.documentToObjectOptions) as Entity;
}
Expand Down Expand Up @@ -288,7 +313,7 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
}
}

addRelations<Relation>(): Promise<Entity> {
addRelations(): Promise<Entity> {
throw new Error('Not implemented yet');
}

Expand Down Expand Up @@ -326,32 +351,35 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
filter: Filter<Relation>,
): Promise<number>;

countRelations<Relation>(): Promise<number | Map<Entity, number>> {
countRelations(): Promise<number | Map<Entity, number>> {
throw new Error('Not implemented yet');
}

findRelation<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dtos: Entity[],
opts?: FindRelationOptions<Relation>,
): Promise<Map<Entity, Relation | undefined>>;
findRelation<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: Entity,
opts?: FindRelationOptions<Relation>,
): Promise<Relation | undefined>;
findRelation<Relation>(
RelationClass: Class<Relation>,
relationName: string,
dto: Entity | Entity[],
opts?: FindRelationOptions<Relation>,
): Promise<(Relation | undefined) | Map<Entity, Relation | undefined>> {
const relationModel = this.Model.model(RelationClass.name);
const dtos: Entity[] = Array.isArray(dto) ? dto : [dto];
return dtos.reduce(async (prev, curr) => {
const map = await prev;
const referenceId = curr[relationName as keyof Entity];
if (referenceId) {
const relationDoc = await relationModel.findOne(referenceId);
const relationDoc = await relationModel.findOne(this.mergeFilterWithId(referenceId, opts?.filter));
map.set(curr, relationDoc?.toObject(this.documentToObjectOptions));
}
return map;
Expand Down Expand Up @@ -405,15 +433,15 @@ export class TypegooseQueryService<Entity> implements QueryService<Entity> {
}, Promise.resolve(new Map<Entity, Relation[]>()));
}

removeRelation<Relation>(): Promise<Entity> {
removeRelation(): Promise<Entity> {
throw new Error('Not implemented yet');
}

removeRelations<Relation>(): Promise<Entity> {
removeRelations(): Promise<Entity> {
throw new Error('Not implemented yet');
}

setRelation<Relation>(): Promise<Entity> {
setRelation(): Promise<Entity> {
throw new Error('Not implemented yet');
}
}

0 comments on commit de34e92

Please sign in to comment.