From 9ff25431e7ae38fe776de0bac37a5665b65b73de Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Fri, 21 Jun 2024 10:38:53 -0400 Subject: [PATCH 1/5] chore: OpenShift Deployment (#31) * Remove un-necessary github actions * Remove saving of graphql schema file --- .github/workflows/on-push-main.yaml | 35 ----------------------------- .github/workflows/on-release.yaml | 28 ----------------------- src/app.module.ts | 3 +-- 3 files changed, 1 insertion(+), 65 deletions(-) delete mode 100644 .github/workflows/on-push-main.yaml delete mode 100644 .github/workflows/on-release.yaml diff --git a/.github/workflows/on-push-main.yaml b/.github/workflows/on-push-main.yaml deleted file mode 100644 index 6b89c9e..0000000 --- a/.github/workflows/on-push-main.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: Handle Push to Main - -on: - push: - branches: [ main ] - -jobs: - docker: - runs-on: ubuntu-latest - name: Build And Push to Docker Hub - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v3 - with: - push: true - tags: hicsail/damplab-backend:unstable - - - name: Push to Staging - uses: fjogeleit/http-request-action@v1 - with: - method: 'POST' - url: ${{ secrets.PORTAINER_WEBHOOK }} - preventFailureOnNoResponse: true diff --git a/.github/workflows/on-release.yaml b/.github/workflows/on-release.yaml deleted file mode 100644 index 857abd1..0000000 --- a/.github/workflows/on-release.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Release Actions - -on: - release: - types: [ published ] - -jobs: - docker: - runs-on: ubuntu-latest - name: Build And Push to Docker Hub - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - uses: docker/build-push-action@v3 - with: - push: true - tags: hicsail/damplab-backend:${{github.ref_name}},hicsail/damplab-backend:latest diff --git a/src/app.module.ts b/src/app.module.ts index 46d2ddc..05b8dc6 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -6,7 +6,6 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import config from './config'; import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; -import { join } from 'path'; import { DampLabServicesModule } from './services/damplab-services.module'; import { MongooseModule } from '@nestjs/mongoose'; import { WorkflowModule } from './workflow/workflow.module'; @@ -22,7 +21,7 @@ import { CommentModule } from './comment/comment.module'; getConfigModule(), GraphQLModule.forRoot({ driver: ApolloDriver, - autoSchemaFile: join(process.cwd(), 'dist/schema.gql') + autoSchemaFile: true }), // Load the MongoDB connection based on the config service From 7ef6d88507b6375c8a11a7196ba348913c29960b Mon Sep 17 00:00:00 2001 From: asad <60375185+am5815@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:18:13 -0400 Subject: [PATCH 2/5] Adhoc updates (#32) # Description > Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. ## Checklist - [ ] This PR can be reviewed in under 30 minutes - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] I have assigned reviewers to this PR. --- scripts/src/commands/load/all.ts | 1 - scripts/src/commands/reset/index.ts | 1 + src/reset/dtos/service.dto.ts | 3 +++ src/reset/reset.service.ts | 3 ++- src/services/models/damplab-service.model.ts | 4 ++++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/src/commands/load/all.ts b/scripts/src/commands/load/all.ts index f59516d..531e0c7 100644 --- a/scripts/src/commands/load/all.ts +++ b/scripts/src/commands/load/all.ts @@ -92,7 +92,6 @@ export default class LoadDatabase extends Command { async insertServices(services: any[]): Promise> { /** Maps the ID as defined in the JSON file to the ID in the database */ const serviceMap = new Map(); - // For each of the services, save the info not including // the allowed connections for (const service of services) { diff --git a/scripts/src/commands/reset/index.ts b/scripts/src/commands/reset/index.ts index f4a3914..e20977e 100644 --- a/scripts/src/commands/reset/index.ts +++ b/scripts/src/commands/reset/index.ts @@ -17,6 +17,7 @@ export default class Reset extends Command { const { flags } = await this.parse(Reset); // Connect to the database + console.log(`${flags.db} connection url`); const client = await MongoClient.connect(flags.db); await client.connect(); diff --git a/src/reset/dtos/service.dto.ts b/src/reset/dtos/service.dto.ts index 025d0b7..f28abb5 100644 --- a/src/reset/dtos/service.dto.ts +++ b/src/reset/dtos/service.dto.ts @@ -29,4 +29,7 @@ export class ServiceInput { @Field(() => [String], { nullable: true }) resultParams?: string[]; + + @Field(() => JSON, { nullable: true }) + paramGroups?: any[]; } diff --git a/src/reset/reset.service.ts b/src/reset/reset.service.ts index 92f3469..92f1dcc 100644 --- a/src/reset/reset.service.ts +++ b/src/reset/reset.service.ts @@ -60,7 +60,8 @@ export class ResetService { allowedConnections: [], result: service.result, description: service.description, - resultParams: service.resultParams + resultParams: service.resultParams, + paramGroups: service.paramGroups }); // Update the map diff --git a/src/services/models/damplab-service.model.ts b/src/services/models/damplab-service.model.ts index 9ee825f..a79fb0e 100644 --- a/src/services/models/damplab-service.model.ts +++ b/src/services/models/damplab-service.model.ts @@ -29,6 +29,10 @@ export class DampLabService { @Field(() => JSON, { description: 'Parameters that are part of the service' }) parameters: any; + @Prop({ type: mongoose.Schema.Types.Mixed, required: false }) + @Field(() => JSON, { description: 'If there are grouped parameters', nullable: true }) + paramGroups: any[]; + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: DampLabService.name }] }) @Field(() => [DampLabService], { description: 'List of services this service can connect to' }) allowedConnections: mongoose.Types.ObjectId[]; From 2da2367a40a05a7c77add05b90ac4cd684794dc2 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Thu, 5 Sep 2024 12:36:27 -0400 Subject: [PATCH 3/5] feat: Basic Crud (#33) # 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. --- src/bundles/bundles.module.ts | 3 ++- src/bundles/bundles.pipe.ts | 17 +++++++++++++++++ src/bundles/bundles.resolver.ts | 10 +++++++++- src/bundles/bundles.service.ts | 10 ++++++++++ src/bundles/dtos/update.dto.ts | 8 ++++++++ src/bundles/update.pipe.ts | 17 +++++++++++++++++ src/categories/categories.module.ts | 4 +++- src/categories/categories.pipe.ts | 19 +++++++++++++++++++ src/categories/categories.resolver.ts | 13 ++++++++++++- src/categories/categories.service.ts | 10 ++++++++++ src/categories/dtos/update.dto.ts | 8 ++++++++ src/categories/update.pipe.ts | 17 +++++++++++++++++ src/services/damplab-services.module.ts | 3 ++- src/services/damplab-services.resolver.ts | 13 ++++++++++++- src/services/damplab-services.services.ts | 6 ++++++ src/services/dtos/update.dto.ts | 8 ++++++++ src/services/update.pipe.ts | 17 +++++++++++++++++ 17 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 src/bundles/bundles.pipe.ts create mode 100644 src/bundles/dtos/update.dto.ts create mode 100644 src/bundles/update.pipe.ts create mode 100644 src/categories/categories.pipe.ts create mode 100644 src/categories/dtos/update.dto.ts create mode 100644 src/categories/update.pipe.ts create mode 100644 src/services/dtos/update.dto.ts create mode 100644 src/services/update.pipe.ts diff --git a/src/bundles/bundles.module.ts b/src/bundles/bundles.module.ts index c28eee8..d8af307 100644 --- a/src/bundles/bundles.module.ts +++ b/src/bundles/bundles.module.ts @@ -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 {} diff --git a/src/bundles/bundles.pipe.ts b/src/bundles/bundles.pipe.ts new file mode 100644 index 0000000..466fbc4 --- /dev/null +++ b/src/bundles/bundles.pipe.ts @@ -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> { + constructor(private readonly bundleService: BundlesService) {} + + async transform(value: string): Promise { + const bundle = await this.bundleService.find(value); + + if (!bundle) { + throw new NotFoundException(`Bundle with id ${value} not found`); + } + return bundle; + } +} diff --git a/src/bundles/bundles.resolver.ts b/src/bundles/bundles.resolver.ts index bbee738..ea21235 100644 --- a/src/bundles/bundles.resolver.ts +++ b/src/bundles/bundles.resolver.ts @@ -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 { @@ -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 { + return this.bundlesService.update(bundle, changes); + } + @ResolveField() async services(@Parent() bundle: Bundle): Promise { return this.dampLabServices.findByIds(bundle.services); diff --git a/src/bundles/bundles.service.ts b/src/bundles/bundles.service.ts index ff24a39..2d06d16 100644 --- a/src/bundles/bundles.service.ts +++ b/src/bundles/bundles.service.ts @@ -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) {} + async find(id: string): Promise { + return this.bundleModel.findById(id); + } + async findAll(): Promise { return this.bundleModel.find().exec(); } + + async update(bundle: Bundle, changes: BundleChange): Promise { + await this.bundleModel.updateOne({ _id: bundle.id }, changes); + return (await this.find(bundle.id))!; + } } diff --git a/src/bundles/dtos/update.dto.ts b/src/bundles/dtos/update.dto.ts new file mode 100644 index 0000000..712adc2 --- /dev/null +++ b/src/bundles/dtos/update.dto.ts @@ -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[]; +} diff --git a/src/bundles/update.pipe.ts b/src/bundles/update.pipe.ts new file mode 100644 index 0000000..b6e2453 --- /dev/null +++ b/src/bundles/update.pipe.ts @@ -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> { + constructor(private readonly damplabServicePipe: DampLabServicePipe) {} + + async transform(value: BundleChange): Promise { + // 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; + } +} diff --git a/src/categories/categories.module.ts b/src/categories/categories.module.ts index c3f7b37..46a604e 100644 --- a/src/categories/categories.module.ts +++ b/src/categories/categories.module.ts @@ -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 {} diff --git a/src/categories/categories.pipe.ts b/src/categories/categories.pipe.ts new file mode 100644 index 0000000..f495ea1 --- /dev/null +++ b/src/categories/categories.pipe.ts @@ -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> { + constructor(private readonly categoryService: CategoryService) {} + + async transform(value: string): Promise { + try { + const category = await this.categoryService.find(value); + if (category) { + return category; + } + } catch (e) {} + + throw new NotFoundException(`Category with id ${value} not found`); + } +} diff --git a/src/categories/categories.resolver.ts b/src/categories/categories.resolver.ts index f0e46e1..6d2a35a 100644 --- a/src/categories/categories.resolver.ts +++ b/src/categories/categories.resolver.ts @@ -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 { @@ -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 { + return this.categoryService.update(category, changes); + } + /** * Resolver for the services field of the Category type */ diff --git a/src/categories/categories.service.ts b/src/categories/categories.service.ts index e0d14c1..6f65c24 100644 --- a/src/categories/categories.service.ts +++ b/src/categories/categories.service.ts @@ -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 { @@ -10,4 +11,13 @@ export class CategoryService { async findAll(): Promise { return this.categoryModel.find().exec(); } + + async find(id: string): Promise { + return this.categoryModel.findById(id); + } + + async update(category: Category, change: CategoryChange): Promise { + await this.categoryModel.updateOne({ _id: category._id }, change); + return (await this.find(category._id))!; + } } diff --git a/src/categories/dtos/update.dto.ts b/src/categories/dtos/update.dto.ts new file mode 100644 index 0000000..d8742d3 --- /dev/null +++ b/src/categories/dtos/update.dto.ts @@ -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[]; +} diff --git a/src/categories/update.pipe.ts b/src/categories/update.pipe.ts new file mode 100644 index 0000000..6ff1fd8 --- /dev/null +++ b/src/categories/update.pipe.ts @@ -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> { + constructor(private readonly damplabServicePipe: DampLabServicePipe) {} + + async transform(value: CategoryChange): Promise { + // 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; + } +} diff --git a/src/services/damplab-services.module.ts b/src/services/damplab-services.module.ts index f5717f1..8011782 100644 --- a/src/services/damplab-services.module.ts +++ b/src/services/damplab-services.module.ts @@ -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 {} diff --git a/src/services/damplab-services.resolver.ts b/src/services/damplab-services.resolver.ts index 6a2e02d..5013466 100644 --- a/src/services/damplab-services.resolver.ts +++ b/src/services/damplab-services.resolver.ts @@ -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 { @@ -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 { + return this.dampLabServices.update(service, changes); + } + /** * Resolver which the `allowedConnections` field of the `DampLabService` * type. Allows for the recursive search on possible connections. diff --git a/src/services/damplab-services.services.ts b/src/services/damplab-services.services.ts index 3b15030..5d9e6ed 100644 --- a/src/services/damplab-services.services.ts +++ b/src/services/damplab-services.services.ts @@ -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 { @@ -22,4 +23,9 @@ export class DampLabServices { async findOne(id: string): Promise { return this.dampLabServiceModel.findById(id).exec(); } + + async update(service: DampLabService, changes: ServiceChange): Promise { + await this.dampLabServiceModel.updateOne({ _id: service._id }, changes); + return (await this.dampLabServiceModel.findById(service._id))!; + } } diff --git a/src/services/dtos/update.dto.ts b/src/services/dtos/update.dto.ts new file mode 100644 index 0000000..2084491 --- /dev/null +++ b/src/services/dtos/update.dto.ts @@ -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[]; +} diff --git a/src/services/update.pipe.ts b/src/services/update.pipe.ts new file mode 100644 index 0000000..8341ed4 --- /dev/null +++ b/src/services/update.pipe.ts @@ -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> { + constructor(private readonly damplabServicePipe: DampLabServicePipe) {} + + async transform(value: ServiceChange): Promise { + // 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; + } +} From 1bd1240af58a0f42400aaddbea9999c44a11c267 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Wed, 11 Sep 2024 10:54:38 -0400 Subject: [PATCH 4/5] feat: Categories Create and Delete (#34) # Description * Deletion of categories * Creation of new categories ## 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. --- src/categories/categories.resolver.ts | 13 +++++++++++++ src/categories/categories.service.ts | 9 +++++++++ src/categories/create.pipe.ts | 17 +++++++++++++++++ src/categories/dtos/create.dto.ts | 8 ++++++++ 4 files changed, 47 insertions(+) create mode 100644 src/categories/create.pipe.ts create mode 100644 src/categories/dtos/create.dto.ts diff --git a/src/categories/categories.resolver.ts b/src/categories/categories.resolver.ts index 6d2a35a..fae5872 100644 --- a/src/categories/categories.resolver.ts +++ b/src/categories/categories.resolver.ts @@ -6,6 +6,8 @@ import { DampLabService } from '../services/models/damplab-service.model'; import { CategoryPipe } from './categories.pipe'; import { CategoryChange } from './dtos/update.dto'; import { CategoryUpdatePipe } from './update.pipe'; +import { CreateCategory } from './dtos/create.dto'; +import { CreateCategoryPipe } from './create.pipe'; @Resolver(() => Category) export class CategoryResolver { @@ -24,6 +26,17 @@ export class CategoryResolver { return this.categoryService.update(category, changes); } + @Mutation(() => Boolean) + async deleteCategory(@Args('category', { type: () => ID }, CategoryPipe) category: Category): Promise { + await this.categoryService.delete(category); + return true; + } + + @Mutation(() => Category) + async createCategory(@Args('category', CreateCategoryPipe) category: CreateCategory): Promise { + return this.categoryService.create(category); + } + /** * Resolver for the services field of the Category type */ diff --git a/src/categories/categories.service.ts b/src/categories/categories.service.ts index 6f65c24..e39caf8 100644 --- a/src/categories/categories.service.ts +++ b/src/categories/categories.service.ts @@ -3,6 +3,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Category, CategoryDocument } from './category.model'; import { Model } from 'mongoose'; import { CategoryChange } from './dtos/update.dto'; +import { CreateCategory } from './dtos/create.dto'; @Injectable() export class CategoryService { @@ -20,4 +21,12 @@ export class CategoryService { await this.categoryModel.updateOne({ _id: category._id }, change); return (await this.find(category._id))!; } + + async delete(category: Category): Promise { + await this.categoryModel.deleteOne({ _id: category._id }); + } + + async create(category: CreateCategory): Promise { + return this.categoryModel.create(category); + } } diff --git a/src/categories/create.pipe.ts b/src/categories/create.pipe.ts new file mode 100644 index 0000000..be8a98c --- /dev/null +++ b/src/categories/create.pipe.ts @@ -0,0 +1,17 @@ +import { Injectable, PipeTransform } from '@nestjs/common'; +import { DampLabServicePipe } from '../services/damplab-services.pipe'; +import { CreateCategory } from './dtos/create.dto'; + +@Injectable() +export class CreateCategoryPipe implements PipeTransform> { + constructor(private readonly damplabServicePipe: DampLabServicePipe) {} + + async transform(value: CreateCategory): Promise { + // Ensure the services are valid + for (const service of value.services) { + await this.damplabServicePipe.transform(service); + } + + return value; + } +} diff --git a/src/categories/dtos/create.dto.ts b/src/categories/dtos/create.dto.ts new file mode 100644 index 0000000..fe7a033 --- /dev/null +++ b/src/categories/dtos/create.dto.ts @@ -0,0 +1,8 @@ +import { ID, InputType, OmitType, Field } from '@nestjs/graphql'; +import { Category } from '../category.model'; + +@InputType() +export class CreateCategory extends OmitType(Category, ['_id', 'services'] as const, InputType) { + @Field(() => [ID]) + services: string[]; +} From cd943e8fa42873cd67e9c8a20ef2606996de69f9 Mon Sep 17 00:00:00 2001 From: Collin Bolles Date: Thu, 12 Sep 2024 13:02:24 -0400 Subject: [PATCH 5/5] feat: Services CRUD (#35) # Description * Create service * Delete service ## 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. --- src/services/create.pipe.ts | 17 +++++++++++++++++ src/services/damplab-services.module.ts | 3 ++- src/services/damplab-services.resolver.ts | 13 +++++++++++++ src/services/damplab-services.services.ts | 17 +++++++++++++++++ src/services/dtos/create.dto.ts | 8 ++++++++ 5 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/services/create.pipe.ts create mode 100644 src/services/dtos/create.dto.ts diff --git a/src/services/create.pipe.ts b/src/services/create.pipe.ts new file mode 100644 index 0000000..c2b3ea2 --- /dev/null +++ b/src/services/create.pipe.ts @@ -0,0 +1,17 @@ +import { Injectable, PipeTransform } from '@nestjs/common'; +import { DampLabServicePipe } from '../services/damplab-services.pipe'; +import { CreateService } from './dtos/create.dto'; + +@Injectable() +export class CreateServicePipe implements PipeTransform> { + constructor(private readonly damplabServicePipe: DampLabServicePipe) {} + + async transform(value: CreateService): Promise { + // Ensure the services are valid + for (const service of value.allowedConnections) { + await this.damplabServicePipe.transform(service); + } + + return value; + } +} diff --git a/src/services/damplab-services.module.ts b/src/services/damplab-services.module.ts index 8011782..a333dd9 100644 --- a/src/services/damplab-services.module.ts +++ b/src/services/damplab-services.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; +import { CreateServicePipe } from './create.pipe'; import { DampLabServicePipe } from './damplab-services.pipe'; import { DampLabServicesResolver } from './damplab-services.resolver'; import { DampLabServices } from './damplab-services.services'; @@ -8,7 +9,7 @@ import { ServiceUpdatePipe } from './update.pipe'; @Module({ imports: [MongooseModule.forFeature([{ name: DampLabService.name, schema: DampLabServiceSchema }])], - providers: [DampLabServicesResolver, DampLabServices, DampLabServicePipe, ServiceUpdatePipe], + providers: [DampLabServicesResolver, DampLabServices, DampLabServicePipe, ServiceUpdatePipe, CreateServicePipe], exports: [DampLabServices, DampLabServicePipe] }) export class DampLabServicesModule {} diff --git a/src/services/damplab-services.resolver.ts b/src/services/damplab-services.resolver.ts index 5013466..0741b3f 100644 --- a/src/services/damplab-services.resolver.ts +++ b/src/services/damplab-services.resolver.ts @@ -1,6 +1,8 @@ import { Resolver, Query, ResolveField, Parent, ID, Args, Mutation } from '@nestjs/graphql'; +import { CreateServicePipe } from './create.pipe'; import { DampLabServicePipe } from './damplab-services.pipe'; import { DampLabServices } from './damplab-services.services'; +import { CreateService } from './dtos/create.dto'; import { ServiceChange } from './dtos/update.dto'; import { DampLabService } from './models/damplab-service.model'; import { ServiceUpdatePipe } from './update.pipe'; @@ -22,6 +24,17 @@ export class DampLabServicesResolver { return this.dampLabServices.update(service, changes); } + @Mutation(() => Boolean) + async deleteService(@Args('service', { type: () => ID }, DampLabServicePipe) service: DampLabService): Promise { + await this.dampLabServices.delete(service); + return true; + } + + @Mutation(() => DampLabService) + async createService(@Args('service', CreateServicePipe) service: CreateService): Promise { + return this.dampLabServices.create(service); + } + /** * Resolver which the `allowedConnections` field of the `DampLabService` * type. Allows for the recursive search on possible connections. diff --git a/src/services/damplab-services.services.ts b/src/services/damplab-services.services.ts index 5d9e6ed..96800e9 100644 --- a/src/services/damplab-services.services.ts +++ b/src/services/damplab-services.services.ts @@ -4,6 +4,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import mongoose from 'mongoose'; import { ServiceChange } from './dtos/update.dto'; +import { CreateService } from './dtos/create.dto'; @Injectable() export class DampLabServices { @@ -28,4 +29,20 @@ export class DampLabServices { await this.dampLabServiceModel.updateOne({ _id: service._id }, changes); return (await this.dampLabServiceModel.findById(service._id))!; } + + async delete(service: DampLabService): Promise { + // Remove all allowed connections first + await this.dampLabServiceModel.updateMany( + {}, + { + $pull: { allowedConnections: service._id } + } + ); + + await this.dampLabServiceModel.deleteOne({ _id: service._id }); + } + + async create(service: CreateService): Promise { + return this.dampLabServiceModel.create(service); + } } diff --git a/src/services/dtos/create.dto.ts b/src/services/dtos/create.dto.ts new file mode 100644 index 0000000..db853b8 --- /dev/null +++ b/src/services/dtos/create.dto.ts @@ -0,0 +1,8 @@ +import { ID, InputType, Field, OmitType } from '@nestjs/graphql'; +import { DampLabService } from '../models/damplab-service.model'; + +@InputType() +export class CreateService extends OmitType(DampLabService, ['_id', 'allowedConnections'] as const, InputType) { + @Field(() => [ID]) + allowedConnections: string[]; +}