Skip to content

Commit

Permalink
feat: Basic Crud (#33)
Browse files Browse the repository at this point in the history
# Description

* GraphQL updates for basic CRUD on
  * Service
  * Category
  * Bundle
* Service list validation before update

## Checklist

- [x] This PR can be reviewed in under 30 minutes
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] I have assigned reviewers to this PR.
  • Loading branch information
cbolles authored Sep 5, 2024
1 parent 7ef6d88 commit 2da2367
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 6 deletions.
3 changes: 2 additions & 1 deletion src/bundles/bundles.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { DampLabServicesModule } from '../services/damplab-services.module';
import { Bundle, BundleSchema } from './bundles.model';
import { BundlesResolver } from './bundles.resolver';
import { BundlesService } from './bundles.service';
import { BundleUpdatePipe } from './update.pipe';

@Module({
imports: [MongooseModule.forFeature([{ name: Bundle.name, schema: BundleSchema }]), DampLabServicesModule],
providers: [BundlesService, BundlesResolver]
providers: [BundlesService, BundlesResolver, BundleUpdatePipe]
})
export class BundlesModule {}
17 changes: 17 additions & 0 deletions src/bundles/bundles.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable, NotFoundException, PipeTransform } from '@nestjs/common';
import { Bundle } from './bundles.model';
import { BundlesService } from './bundles.service';

@Injectable()
export class BundlesPipe implements PipeTransform<string, Promise<Bundle>> {
constructor(private readonly bundleService: BundlesService) {}

async transform(value: string): Promise<Bundle> {
const bundle = await this.bundleService.find(value);

if (!bundle) {
throw new NotFoundException(`Bundle with id ${value} not found`);
}
return bundle;
}
}
10 changes: 9 additions & 1 deletion src/bundles/bundles.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { Parent, Query, ResolveField, Resolver, Args, Mutation, ID } from '@nestjs/graphql';
import { DampLabServices } from '../services/damplab-services.services';
import { DampLabService } from '../services/models/damplab-service.model';
import { Bundle } from './bundles.model';
import { BundlesService } from './bundles.service';
import { BundlesPipe } from './bundles.pipe';
import { BundleChange } from './dtos/update.dto';
import { BundleUpdatePipe } from './update.pipe';

@Resolver(() => Bundle)
export class BundlesResolver {
Expand All @@ -13,6 +16,11 @@ export class BundlesResolver {
return this.bundlesService.findAll();
}

@Mutation(() => Bundle)
async updateBundle(@Args('bundle', { type: () => ID }, BundlesPipe) bundle: Bundle, @Args('changes', { type: () => BundleChange }, BundleUpdatePipe) changes: BundleChange): Promise<Bundle> {
return this.bundlesService.update(bundle, changes);
}

@ResolveField()
async services(@Parent() bundle: Bundle): Promise<DampLabService[]> {
return this.dampLabServices.findByIds(bundle.services);
Expand Down
10 changes: 10 additions & 0 deletions src/bundles/bundles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Bundle } from './bundles.model';
import { Model } from 'mongoose';
import { BundleChange } from './dtos/update.dto';

@Injectable()
export class BundlesService {
constructor(@InjectModel(Bundle.name) private readonly bundleModel: Model<Bundle>) {}

async find(id: string): Promise<Bundle | null> {
return this.bundleModel.findById(id);
}

async findAll(): Promise<Bundle[]> {
return this.bundleModel.find().exec();
}

async update(bundle: Bundle, changes: BundleChange): Promise<Bundle> {
await this.bundleModel.updateOne({ _id: bundle.id }, changes);
return (await this.find(bundle.id))!;
}
}
8 changes: 8 additions & 0 deletions src/bundles/dtos/update.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Bundle } from '../bundles.model';
import { ID, InputType, OmitType, PartialType, Field } from '@nestjs/graphql';

@InputType()
export class BundleChange extends PartialType(OmitType(Bundle, ['id', 'services'] as const), InputType) {
@Field(() => [ID], { nullable: true })
services: string[];
}
17 changes: 17 additions & 0 deletions src/bundles/update.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable, PipeTransform } from '@nestjs/common';
import { DampLabServicePipe } from '../services/damplab-services.pipe';
import { BundleChange } from './dtos/update.dto';

@Injectable()
export class BundleUpdatePipe implements PipeTransform<BundleChange, Promise<BundleChange>> {
constructor(private readonly damplabServicePipe: DampLabServicePipe) {}

async transform(value: BundleChange): Promise<BundleChange> {
// If services is includes, make sure they are all valid
if (value.services) {
await Promise.all(value.services.map((service) => this.damplabServicePipe.transform(service)));
}

return value;
}
}
4 changes: 3 additions & 1 deletion src/categories/categories.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Category, CategorySchema } from './category.model';
import { CategoryService } from './categories.service';
import { CategoryResolver } from './categories.resolver';
import { DampLabServicesModule } from '../services/damplab-services.module';
import { CategoryPipe } from './categories.pipe';
import { CategoryUpdatePipe } from './update.pipe';

@Module({
imports: [MongooseModule.forFeature([{ name: Category.name, schema: CategorySchema }]), DampLabServicesModule],
providers: [CategoryService, CategoryResolver]
providers: [CategoryService, CategoryResolver, CategoryPipe, CategoryUpdatePipe]
})
export class CategoriesModule {}
19 changes: 19 additions & 0 deletions src/categories/categories.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NotFoundException, Injectable, PipeTransform } from '@nestjs/common';
import { Category } from './category.model';
import { CategoryService } from './categories.service';

@Injectable()
export class CategoryPipe implements PipeTransform<string, Promise<Category>> {
constructor(private readonly categoryService: CategoryService) {}

async transform(value: string): Promise<Category> {
try {
const category = await this.categoryService.find(value);
if (category) {
return category;
}
} catch (e) {}

throw new NotFoundException(`Category with id ${value} not found`);
}
}
13 changes: 12 additions & 1 deletion src/categories/categories.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Query, ResolveField, Resolver } from '@nestjs/graphql';
import { Args, Mutation, Query, ResolveField, Resolver, ID } from '@nestjs/graphql';
import { Category } from './category.model';
import { CategoryService } from './categories.service';
import { DampLabServices } from '../services/damplab-services.services';
import { DampLabService } from '../services/models/damplab-service.model';
import { CategoryPipe } from './categories.pipe';
import { CategoryChange } from './dtos/update.dto';
import { CategoryUpdatePipe } from './update.pipe';

@Resolver(() => Category)
export class CategoryResolver {
Expand All @@ -13,6 +16,14 @@ export class CategoryResolver {
return this.categoryService.findAll();
}

@Mutation(() => Category)
async updateCategory(
@Args('category', { type: () => ID }, CategoryPipe) category: Category,
@Args('changes', { type: () => CategoryChange }, CategoryUpdatePipe) changes: CategoryChange
): Promise<Category> {
return this.categoryService.update(category, changes);
}

/**
* Resolver for the services field of the Category type
*/
Expand Down
10 changes: 10 additions & 0 deletions src/categories/categories.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Category, CategoryDocument } from './category.model';
import { Model } from 'mongoose';
import { CategoryChange } from './dtos/update.dto';

@Injectable()
export class CategoryService {
Expand All @@ -10,4 +11,13 @@ export class CategoryService {
async findAll(): Promise<Category[]> {
return this.categoryModel.find().exec();
}

async find(id: string): Promise<Category | null> {
return this.categoryModel.findById(id);
}

async update(category: Category, change: CategoryChange): Promise<Category> {
await this.categoryModel.updateOne({ _id: category._id }, change);
return (await this.find(category._id))!;
}
}
8 changes: 8 additions & 0 deletions src/categories/dtos/update.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Category } from '../category.model';
import { ID, OmitType, PartialType, Field, InputType } from '@nestjs/graphql';

@InputType()
export class CategoryChange extends PartialType(OmitType(Category, ['_id', 'services'] as const), InputType) {
@Field(() => [ID], { nullable: true })
services: string[];
}
17 changes: 17 additions & 0 deletions src/categories/update.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable, PipeTransform } from '@nestjs/common';
import { DampLabServicePipe } from '../services/damplab-services.pipe';
import { CategoryChange } from './dtos/update.dto';

@Injectable()
export class CategoryUpdatePipe implements PipeTransform<CategoryChange, Promise<CategoryChange>> {
constructor(private readonly damplabServicePipe: DampLabServicePipe) {}

async transform(value: CategoryChange): Promise<CategoryChange> {
// If services is includes, make sure they are all valid
if (value.services) {
await Promise.all(value.services.map((service) => this.damplabServicePipe.transform(service)));
}

return value;
}
}
3 changes: 2 additions & 1 deletion src/services/damplab-services.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { DampLabServicePipe } from './damplab-services.pipe';
import { DampLabServicesResolver } from './damplab-services.resolver';
import { DampLabServices } from './damplab-services.services';
import { DampLabService, DampLabServiceSchema } from './models/damplab-service.model';
import { ServiceUpdatePipe } from './update.pipe';

@Module({
imports: [MongooseModule.forFeature([{ name: DampLabService.name, schema: DampLabServiceSchema }])],
providers: [DampLabServicesResolver, DampLabServices, DampLabServicePipe],
providers: [DampLabServicesResolver, DampLabServices, DampLabServicePipe, ServiceUpdatePipe],
exports: [DampLabServices, DampLabServicePipe]
})
export class DampLabServicesModule {}
13 changes: 12 additions & 1 deletion src/services/damplab-services.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Resolver, Query, ResolveField, Parent } from '@nestjs/graphql';
import { Resolver, Query, ResolveField, Parent, ID, Args, Mutation } from '@nestjs/graphql';
import { DampLabServicePipe } from './damplab-services.pipe';
import { DampLabServices } from './damplab-services.services';
import { ServiceChange } from './dtos/update.dto';
import { DampLabService } from './models/damplab-service.model';
import { ServiceUpdatePipe } from './update.pipe';

@Resolver(() => DampLabService)
export class DampLabServicesResolver {
Expand All @@ -11,6 +14,14 @@ export class DampLabServicesResolver {
return this.dampLabServices.findAll();
}

@Mutation(() => DampLabService)
async updateService(
@Args('service', { type: () => ID }, DampLabServicePipe) service: DampLabService,
@Args('changes', { type: () => ServiceChange }, ServiceUpdatePipe) changes: ServiceChange
): Promise<DampLabService> {
return this.dampLabServices.update(service, changes);
}

/**
* Resolver which the `allowedConnections` field of the `DampLabService`
* type. Allows for the recursive search on possible connections.
Expand Down
6 changes: 6 additions & 0 deletions src/services/damplab-services.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DampLabService, DampLabServiceDocument } from './models/damplab-service
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import mongoose from 'mongoose';
import { ServiceChange } from './dtos/update.dto';

@Injectable()
export class DampLabServices {
Expand All @@ -22,4 +23,9 @@ export class DampLabServices {
async findOne(id: string): Promise<DampLabService | null> {
return this.dampLabServiceModel.findById(id).exec();
}

async update(service: DampLabService, changes: ServiceChange): Promise<DampLabService> {
await this.dampLabServiceModel.updateOne({ _id: service._id }, changes);
return (await this.dampLabServiceModel.findById(service._id))!;
}
}
8 changes: 8 additions & 0 deletions src/services/dtos/update.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DampLabService } from '../models/damplab-service.model';
import { ID, InputType, OmitType, PartialType, Field } from '@nestjs/graphql';

@InputType()
export class ServiceChange extends PartialType(OmitType(DampLabService, ['_id', 'allowedConnections'] as const), InputType) {
@Field(() => [ID], { nullable: true })
allowedConnections: string[];
}
17 changes: 17 additions & 0 deletions src/services/update.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable, PipeTransform } from '@nestjs/common';
import { DampLabServicePipe } from '../services/damplab-services.pipe';
import { ServiceChange } from './dtos/update.dto';

@Injectable()
export class ServiceUpdatePipe implements PipeTransform<ServiceChange, Promise<ServiceChange>> {
constructor(private readonly damplabServicePipe: DampLabServicePipe) {}

async transform(value: ServiceChange): Promise<ServiceChange> {
// If services is includes, make sure they are all valid
if (value.allowedConnections) {
await Promise.all(value.allowedConnections.map((service) => this.damplabServicePipe.transform(service)));
}

return value;
}
}

0 comments on commit 2da2367

Please sign in to comment.