From 78a6b4a9728079f21920a1be97d96de0b85fb145 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Fri, 25 Oct 2024 15:30:26 +0200 Subject: [PATCH 1/6] Feature 253: only allow workgroup change if asset has no references --- apps/client-asset-sg/src/app/i18n/de.ts | 1 + apps/client-asset-sg/src/app/i18n/en.ts | 1 + apps/client-asset-sg/src/app/i18n/fr.ts | 1 + apps/client-asset-sg/src/app/i18n/it.ts | 1 + apps/client-asset-sg/src/app/i18n/rm.ts | 1 + .../src/features/asset-edit/asset-edit.http | 3 +- .../features/asset-edit/asset-edit.repo.ts | 42 +++++++++++++++ .../features/asset-edit/asset-edit.service.ts | 53 +++++++++++++++++++ .../asset-editor-tab-general.component.html | 4 ++ .../asset-editor-tab-general.component.ts | 22 +++++++- ...asset-editor-tab-references.component.html | 6 +-- .../asset-editor-tab-references.component.ts | 32 ++++++----- 12 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts diff --git a/apps/client-asset-sg/src/app/i18n/de.ts b/apps/client-asset-sg/src/app/i18n/de.ts index 4208803b..bc576ff4 100644 --- a/apps/client-asset-sg/src/app/i18n/de.ts +++ b/apps/client-asset-sg/src/app/i18n/de.ts @@ -124,6 +124,7 @@ export const deAppTranslations = { alternativeId: 'Alternativ-ID', alternativeIdDescription: 'Beschreibung Alternativ-ID', addNewAlternativeId: 'Neue Alternativ-ID hinzufügen', + referencesWarning: 'Um die Arbeitsgruppe zu ändern, müssen Sie erst alle Verweise entfernen.', }, files: { tabName: 'Dateien', diff --git a/apps/client-asset-sg/src/app/i18n/en.ts b/apps/client-asset-sg/src/app/i18n/en.ts index 96d50d6e..e1af8f4a 100644 --- a/apps/client-asset-sg/src/app/i18n/en.ts +++ b/apps/client-asset-sg/src/app/i18n/en.ts @@ -125,6 +125,7 @@ export const enAppTranslations: AppTranslations = { alternativeId: 'Alternative ID', alternativeIdDescription: 'Alternative ID Description', addNewAlternativeId: 'Add new alternative ID', + referencesWarning: 'In order to change the workgroup, you must first remove all references.', }, files: { tabName: 'Files', diff --git a/apps/client-asset-sg/src/app/i18n/fr.ts b/apps/client-asset-sg/src/app/i18n/fr.ts index 7d398311..bfcd8591 100644 --- a/apps/client-asset-sg/src/app/i18n/fr.ts +++ b/apps/client-asset-sg/src/app/i18n/fr.ts @@ -126,6 +126,7 @@ export const frAppTranslations: AppTranslations = { alternativeId: 'ID alternative', alternativeIdDescription: "Description d'ID alternative", addNewAlternativeId: 'Ajouter une nouvelle ID alternative', + referencesWarning: 'Pour changer le groupe de travail, vous devez d’abord supprimer toutes les références.', }, files: { tabName: 'Fichiers', diff --git a/apps/client-asset-sg/src/app/i18n/it.ts b/apps/client-asset-sg/src/app/i18n/it.ts index b49f2daa..5d87fb10 100644 --- a/apps/client-asset-sg/src/app/i18n/it.ts +++ b/apps/client-asset-sg/src/app/i18n/it.ts @@ -125,6 +125,7 @@ export const itAppTranslations: AppTranslations = { alternativeId: 'IT Alternativ-ID', alternativeIdDescription: 'IT Beschreibung Alternativ-ID', addNewAlternativeId: 'IT Neue Alternativ-ID hinzufügen', + referencesWarning: 'IT Um die Arbeitsgruppe zu ändern, müssen Sie erst alle Verweise entfernen.', }, files: { tabName: 'IT Dateien', diff --git a/apps/client-asset-sg/src/app/i18n/rm.ts b/apps/client-asset-sg/src/app/i18n/rm.ts index e81baeb6..a5bd82d3 100644 --- a/apps/client-asset-sg/src/app/i18n/rm.ts +++ b/apps/client-asset-sg/src/app/i18n/rm.ts @@ -125,6 +125,7 @@ export const rmAppTranslations: AppTranslations = { alternativeId: 'RM Alternativ-ID', alternativeIdDescription: 'RM Beschreibung Alternativ-ID', addNewAlternativeId: 'RM Neue Alternativ-ID hinzufügen', + referencesWarning: 'RM Um die Arbeitsgruppe zu ändern, müssen Sie erst alle Verweise entfernen.', }, files: { tabName: 'RM Dateien', diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.http b/apps/server-asset-sg/src/features/asset-edit/asset-edit.http index c421a664..d6a9ac66 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.http +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.http @@ -35,7 +35,8 @@ Content-Type: application/json "titleOriginal": "My Cool Asset", "titlePublic": "Our Cool Asset", "typeNatRels": [], - "workgroupId": 1 + "workgroupId": 1, + "assetFiles": [] } ### Update asset-edit diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts index 8aa98832..c10e259a 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts @@ -49,6 +49,7 @@ export class AssetEditRepo implements Repo { + await this.validateReferencesOrThrow(data); const asset = await this.prismaService.asset.create({ select: { assetId: true }, data: { @@ -112,6 +113,8 @@ export class AssetEditRepo implements Repo { @@ -317,6 +320,45 @@ export class AssetEditRepo implements Repo { + // check if any of the siblings are in another workgroup + for (const assetYId of data.patch.siblingAssetIds) { + const siblingCandidate = await this.prismaService.asset.findUnique({ + where: { assetId: assetYId }, + select: { assetId: true, workgroupId: true }, + }); + if (siblingCandidate?.workgroupId !== data.patch.workgroupId) { + throw new Error('Sibling assets must be in the same workgroup as the edited asset'); + } + } + + // check if the parent asset is in another workgroup + const assetMainId = O.toUndefined(data.patch.assetMainId); + if (assetMainId) { + const assetMain = await this.prismaService.asset.findUnique({ + where: { assetId: assetMainId }, + select: { assetId: true, workgroupId: true, titlePublic: true }, + }); + if (assetMain?.workgroupId !== data.patch.workgroupId) { + throw new Error('Cannot assign parent asset from different workgroup'); + } + } + + // check if any of the subordinate assets are in another workgroup for exisiting assets + if (id) { + const childAssets = await this.prismaService.asset.findMany({ + where: { assetMainId: id }, + select: { assetId: true, workgroupId: true, titlePublic: true }, + }); + + for (const child of childAssets) { + if (child.workgroupId !== data.patch.workgroupId) { + throw new Error('The subordinate assets must be in the same workgroups as the parent asset'); + } + } + } + } } /** diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts new file mode 100644 index 00000000..82a943b9 --- /dev/null +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts @@ -0,0 +1,53 @@ + +import { isNotNull, unknownToUnknownError } from '@asset-sg/core'; +import { BaseAssetEditDetail, PatchAsset } from '@asset-sg/shared'; +import { User } from '@asset-sg/shared/v2'; +import { Injectable } from '@nestjs/common'; +import { pipe } from 'fp-ts/function'; +import * as TE from 'fp-ts/TaskEither'; +import * as C from 'io-ts/Codec'; + +import { AssetEditRepo } from './asset-edit.repo'; +import { AssetSearchService } from '@/features/assets/search/asset-search.service'; +import { notFoundError } from '@/utils/errors'; + +export const AssetEditDetail = C.struct({ + ...BaseAssetEditDetail, + studies: C.array(C.struct({ assetId: C.number, studyId: C.string, geomText: C.string })), +}); +export type AssetEditDetail = C.TypeOf; + +@Injectable() +export class AssetEditService { + constructor(private readonly assetEditRepo: AssetEditRepo, private readonly assetSearchService: AssetSearchService) {} + + public createAsset(user: User, patch: PatchAsset) { + return pipe( + TE.tryCatch( + () => this.assetEditRepo.create({ user, patch }), + (e) => e as Error + ), + TE.chain(({ assetId }) => + TE.tryCatch( + () => this.assetEditRepo.find(assetId), + (e) => e as Error + ) + ), + TE.chainW(TE.fromPredicate(isNotNull, notFoundError)), + TE.tap((asset) => TE.tryCatch(() => this.assetSearchService.register(asset), unknownToUnknownError)), + TE.map((asset) => AssetEditDetail.encode(asset)) + ); + } + + public updateAsset(user: User, assetId: number, patch: PatchAsset) { + return pipe( + TE.tryCatch( + () => this.assetEditRepo.update(assetId, { user, patch }), + (e) => e as Error + ), + TE.chainW(TE.fromPredicate(isNotNull, notFoundError)), + TE.tap((asset) => TE.tryCatch(() => this.assetSearchService.register(asset), unknownToUnknownError)), + TE.map((asset) => AssetEditDetail.encode(asset)) + ); + } +} diff --git a/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.html b/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.html index 8fe4db56..ef8e72f9 100644 --- a/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.html +++ b/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.html @@ -3,6 +3,10 @@
workgroup.title
+
+ + {{ "edit.tabs.general.referencesWarning" | translate }} +
workgroup.title diff --git a/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.ts b/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.ts index ee48b446..2f0492a5 100644 --- a/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.ts +++ b/libs/asset-editor/src/lib/components/asset-editor-tab-general/asset-editor-tab-general.component.ts @@ -41,7 +41,7 @@ const initialAssetEditorTabGeneralState: AssetEditorTabGeneralState = { }) export class AssetEditorTabGeneralComponent implements OnInit { private readonly rootFormGroupDirective = inject(FormGroupDirective); - private readonly rootFormGroup: AssetEditorFormGroup = this.rootFormGroupDirective.control; + protected readonly rootFormGroup: AssetEditorFormGroup = this.rootFormGroupDirective.control; private readonly formBuilder = inject(FormBuilder); private readonly focusMonitor = inject(FocusMonitor); @@ -56,6 +56,8 @@ export class AssetEditorTabGeneralComponent implements OnInit { description: new FormControl('', { nonNullable: true }), }); + public showWarningForReferences = false; + public readonly state: RxState = inject(RxState); public readonly _referenceDataVM$ = this.state.select('referenceDataVM'); @@ -174,6 +176,10 @@ export class AssetEditorTabGeneralComponent implements OnInit { this.idForm.patchValue({ idId, id, description }); } }); + this.setDisabledStatusOfWorkgroup(); + this.rootFormGroup.controls.references.valueChanges.pipe(untilDestroyed(this)).subscribe(() => { + this.setDisabledStatusOfWorkgroup(); + }); this.ngOnInit$.next(); } @@ -222,5 +228,19 @@ export class AssetEditorTabGeneralComponent implements OnInit { this.idForm.patchValue({ id, description }); } + private setDisabledStatusOfWorkgroup() { + if ( + this.rootFormGroup.getRawValue().references.siblingAssets.length > 0 || + this.rootFormGroup.getRawValue().references.childAssets.length > 0 || + this.rootFormGroup.getRawValue().references.assetMain + ) { + this.form.controls.workgroupId.disable({ emitEvent: false }); + this.showWarningForReferences = true; + } else { + this.form.controls.workgroupId.enable({ emitEvent: false }); + this.showWarningForReferences = false; + } + } + public eqAssetLanguageEdit = eqAssetLanguageEdit; } diff --git a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.html b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.html index e28879d1..f8c75875 100644 --- a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.html +++ b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.html @@ -41,9 +41,9 @@ Typ - {{ - "edit.tabs.references.referenceType.parent" | translate - }} + {{ "edit.tabs.references.referenceType.parent" | translate }} + {{ "edit.tabs.references.referenceType.sibling" | translate }} diff --git a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts index e0218657..800ab91c 100644 --- a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts +++ b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts @@ -1,25 +1,25 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild, inject } from '@angular/core'; +import { ChangeDetectionStrategy, Component, ElementRef, inject, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormControl, FormGroupDirective, Validators } from '@angular/forms'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { httpErrorResponseError } from '@asset-sg/client-shared'; import { unknownToUnknownError } from '@asset-sg/core'; -import { AssetByTitle, LinkedAsset } from '@asset-sg/shared'; +import { AssetByTitle, AssetEditDetail, LinkedAsset } from '@asset-sg/shared'; import { UntilDestroy } from '@ngneat/until-destroy'; import * as A from 'fp-ts/Array'; import * as E from 'fp-ts/Either'; import { flow, pipe } from 'fp-ts/function'; import * as D from 'io-ts/Decoder'; import { - Observable, - Subject, catchError, combineLatest, debounceTime, distinctUntilChanged, map, + Observable, of, startWith, + Subject, switchMap, } from 'rxjs'; @@ -81,17 +81,23 @@ export class AssetEditorTabReferencesComponent implements OnInit { switchMap( (value): Observable => value.length >= 3 - ? this._httpClient.get(`/api/asset-edit/search?title=${value}`).pipe( - catchError((err: HttpErrorResponse | unknown) => - of(err instanceof HttpErrorResponse ? httpErrorResponseError(err) : unknownToUnknownError(err)) - ), - map( - flow( - D.array(AssetByTitle).decode, - E.getOrElseW(() => []) + ? this._httpClient + .post(`/api/assets/search?limit=10`, { + text: value, + workgroupIds: [this.rootFormGroup.getRawValue().general.workgroupId], + }) + .pipe( + map((res) => (res as { data: AssetEditDetail[] }).data), + catchError((err: HttpErrorResponse | unknown) => + of(err instanceof HttpErrorResponse ? httpErrorResponseError(err) : unknownToUnknownError(err)) + ), + map( + flow( + D.array(AssetEditDetail).decode, + E.getOrElseW(() => []) + ) ) ) - ) : of([]) ) ), From 69c178170d1d0df2a2f20eeddb78ab62fddf4bf3 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Fri, 25 Oct 2024 16:07:42 +0200 Subject: [PATCH 2/6] Fix: Parent asset was not properly removed from DB --- .../server-asset-sg/src/features/asset-edit/asset-edit.repo.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts index c10e259a..b8a14516 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts @@ -120,6 +120,7 @@ export class AssetEditRepo implements Repo { // Update the asset. // For any relation, delete the existing records, and insert the updated ones. + console.log(O.toUndefined(data.patch.assetMainId)); await this.prismaService.asset.update({ where: { assetId: id }, data: { @@ -130,7 +131,7 @@ export class AssetEditRepo implements Repo Date: Fri, 25 Oct 2024 16:35:40 +0200 Subject: [PATCH 3/6] Refactor: slightly refactor the way the search is triggered within the component --- .../asset-editor-tab-references.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts index 800ab91c..aa928482 100644 --- a/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts +++ b/libs/asset-editor/src/lib/components/asset-editor-tab-references/asset-editor-tab-references.component.ts @@ -79,7 +79,7 @@ export class AssetEditorTabReferencesComponent implements OnInit { this._authorSearchInput$.pipe( debounceTime(300), switchMap( - (value): Observable => + (value): Observable => value.length >= 3 ? this._httpClient .post(`/api/assets/search?limit=10`, { From 7327debd38b80780c57e039c27ababff22800931 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Tue, 29 Oct 2024 11:03:31 +0100 Subject: [PATCH 4/6] remove console.log --- apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts index b8a14516..008e7ca3 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts @@ -120,7 +120,6 @@ export class AssetEditRepo implements Repo { // Update the asset. // For any relation, delete the existing records, and insert the updated ones. - console.log(O.toUndefined(data.patch.assetMainId)); await this.prismaService.asset.update({ where: { assetId: id }, data: { From 852ef6625aac5262fa43ebf76ab1e14bfbdd2da2 Mon Sep 17 00:00:00 2001 From: till_schuetze Date: Fri, 1 Nov 2024 08:25:38 +0100 Subject: [PATCH 5/6] pr fixes --- .../src/features/asset-edit/asset-edit.repo.ts | 10 +++++----- .../asset-editor-tab-general.component.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts index 008e7ca3..9397621d 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts @@ -130,7 +130,7 @@ export class AssetEditRepo implements Repo Date: Wed, 6 Nov 2024 17:48:36 +0100 Subject: [PATCH 6/6] move validation to service --- apps/server-asset-sg/src/app.module.ts | 2 + .../asset-edit/asset-edit.controller.ts | 10 +- .../features/asset-edit/asset-edit.repo.ts | 43 --------- .../features/asset-edit/asset-edit.service.ts | 92 ++++++++++--------- 4 files changed, 58 insertions(+), 89 deletions(-) diff --git a/apps/server-asset-sg/src/app.module.ts b/apps/server-asset-sg/src/app.module.ts index 2512ceb7..11aa94fb 100644 --- a/apps/server-asset-sg/src/app.module.ts +++ b/apps/server-asset-sg/src/app.module.ts @@ -11,6 +11,7 @@ import { JwtMiddleware } from '@/core/middleware/jwt.middleware'; import { PrismaService } from '@/core/prisma.service'; import { AssetEditController } from '@/features/asset-edit/asset-edit.controller'; import { AssetEditRepo } from '@/features/asset-edit/asset-edit.repo'; +import { AssetEditService } from '@/features/asset-edit/asset-edit.service'; import { AssetInfoRepo } from '@/features/assets/asset-info.repo'; import { AssetRepo } from '@/features/assets/asset.repo'; import { AssetsController } from '@/features/assets/assets.controller'; @@ -51,6 +52,7 @@ import { WorkgroupsController } from '@/features/workgroups/workgroups.controlle provideElasticsearch, AssetEditRepo, AssetInfoRepo, + AssetEditService, AssetRepo, AssetSearchService, AssetSyncService, diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.controller.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.controller.ts index f505b30d..b283a68c 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.controller.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.controller.ts @@ -17,11 +17,16 @@ import { authorize } from '@/core/authorize'; import { CurrentUser } from '@/core/decorators/current-user.decorator'; import { ParseBody } from '@/core/decorators/parse.decorator'; import { AssetEditRepo } from '@/features/asset-edit/asset-edit.repo'; +import { AssetEditService } from '@/features/asset-edit/asset-edit.service'; import { AssetSearchService } from '@/features/assets/search/asset-search.service'; @Controller('/asset-edit') export class AssetEditController { - constructor(private readonly assetEditRepo: AssetEditRepo, private readonly assetSearchService: AssetSearchService) {} + constructor( + private readonly assetEditRepo: AssetEditRepo, + private readonly assetEditService: AssetEditService, + private readonly assetSearchService: AssetSearchService + ) {} @Get('/:id') async show(@Param('id', ParseIntPipe) id: number, @CurrentUser() user: User): Promise { @@ -38,6 +43,8 @@ export class AssetEditController { authorize(AssetEditPolicy, user).canCreate(); validatePatch(user, patch); + await this.assetEditService.validateReferencesOrThrow({ user, patch }); + const asset = await this.assetEditRepo.create({ user, patch }); await this.assetSearchService.register(asset); return AssetEditDetail.encode(asset); @@ -56,6 +63,7 @@ export class AssetEditController { authorize(AssetEditPolicy, user).canUpdate(record); validatePatch(user, patch, record); + await this.assetEditService.validateReferencesOrThrow({ user, patch }, id); const asset = await this.assetEditRepo.update(record.assetId, { user, patch }); if (asset === null) { diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts index 9397621d..bf14a32d 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts @@ -49,7 +49,6 @@ export class AssetEditRepo implements Repo { - await this.validateReferencesOrThrow(data); const asset = await this.prismaService.asset.create({ select: { assetId: true }, data: { @@ -112,9 +111,6 @@ export class AssetEditRepo implements Repo { @@ -320,45 +316,6 @@ export class AssetEditRepo implements Repo { - // check if any of the siblings are in another workgroup - for (const assetYId of data.patch.siblingAssetIds) { - const siblingCandidate = await this.prismaService.asset.findUnique({ - where: { assetId: assetYId }, - select: { workgroupId: true }, - }); - if (siblingCandidate?.workgroupId !== data.patch.workgroupId) { - throw new Error('Sibling assets must be in the same workgroup as the edited asset'); - } - } - - // check if the parent asset is in another workgroup - const assetMainId = O.toUndefined(data.patch.assetMainId); - if (assetMainId) { - const assetMain = await this.prismaService.asset.findUnique({ - where: { assetId: assetMainId }, - select: { workgroupId: true }, - }); - if (assetMain?.workgroupId !== data.patch.workgroupId) { - throw new Error('Cannot assign parent asset from different workgroup'); - } - } - - // check if any of the subordinate assets are in another workgroup for exisiting assets - if (id) { - const childAssets = await this.prismaService.asset.findMany({ - where: { assetMainId: id }, - select: { workgroupId: true }, - }); - - for (const child of childAssets) { - if (child.workgroupId !== data.patch.workgroupId) { - throw new Error('Child assets must be in the same workgroup as the parent asset'); - } - } - } - } } /** diff --git a/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts b/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts index 82a943b9..911d35b7 100644 --- a/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts +++ b/apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts @@ -1,53 +1,55 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import * as O from 'fp-ts/Option'; -import { isNotNull, unknownToUnknownError } from '@asset-sg/core'; -import { BaseAssetEditDetail, PatchAsset } from '@asset-sg/shared'; -import { User } from '@asset-sg/shared/v2'; -import { Injectable } from '@nestjs/common'; -import { pipe } from 'fp-ts/function'; -import * as TE from 'fp-ts/TaskEither'; -import * as C from 'io-ts/Codec'; - -import { AssetEditRepo } from './asset-edit.repo'; -import { AssetSearchService } from '@/features/assets/search/asset-search.service'; -import { notFoundError } from '@/utils/errors'; - -export const AssetEditDetail = C.struct({ - ...BaseAssetEditDetail, - studies: C.array(C.struct({ assetId: C.number, studyId: C.string, geomText: C.string })), -}); -export type AssetEditDetail = C.TypeOf; +import { AssetEditData } from './asset-edit.repo'; +import { PrismaService } from '@/core/prisma.service'; @Injectable() export class AssetEditService { - constructor(private readonly assetEditRepo: AssetEditRepo, private readonly assetSearchService: AssetSearchService) {} + constructor(private readonly prismaService: PrismaService) {} - public createAsset(user: User, patch: PatchAsset) { - return pipe( - TE.tryCatch( - () => this.assetEditRepo.create({ user, patch }), - (e) => e as Error - ), - TE.chain(({ assetId }) => - TE.tryCatch( - () => this.assetEditRepo.find(assetId), - (e) => e as Error - ) - ), - TE.chainW(TE.fromPredicate(isNotNull, notFoundError)), - TE.tap((asset) => TE.tryCatch(() => this.assetSearchService.register(asset), unknownToUnknownError)), - TE.map((asset) => AssetEditDetail.encode(asset)) - ); - } + public async validateReferencesOrThrow(data: AssetEditData, id?: number): Promise { + // check if any of the siblings are in another workgroup + for (const assetYId of data.patch.siblingAssetIds) { + const siblingCandidate = await this.prismaService.asset.findUnique({ + where: { assetId: assetYId }, + select: { workgroupId: true }, + }); + if (siblingCandidate?.workgroupId !== data.patch.workgroupId) { + throw new HttpException( + 'Sibling assets must be in the same workgroup as the edited asset', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + } + + // check if the parent asset is in another workgroup + const assetMainId = O.toUndefined(data.patch.assetMainId); + if (assetMainId) { + const assetMain = await this.prismaService.asset.findUnique({ + where: { assetId: assetMainId }, + select: { workgroupId: true }, + }); + if (assetMain?.workgroupId !== data.patch.workgroupId) { + throw new HttpException('Cannot assign parent asset from different workgroup', HttpStatus.UNPROCESSABLE_ENTITY); + } + } + + // check if any of the subordinate assets are in another workgroup for exisiting assets + if (id) { + const childAssets = await this.prismaService.asset.findMany({ + where: { assetMainId: id }, + select: { workgroupId: true }, + }); - public updateAsset(user: User, assetId: number, patch: PatchAsset) { - return pipe( - TE.tryCatch( - () => this.assetEditRepo.update(assetId, { user, patch }), - (e) => e as Error - ), - TE.chainW(TE.fromPredicate(isNotNull, notFoundError)), - TE.tap((asset) => TE.tryCatch(() => this.assetSearchService.register(asset), unknownToUnknownError)), - TE.map((asset) => AssetEditDetail.encode(asset)) - ); + for (const child of childAssets) { + if (child.workgroupId !== data.patch.workgroupId) { + throw new HttpException( + 'Child assets must be in the same workgroup as the parent asset', + HttpStatus.UNPROCESSABLE_ENTITY + ); + } + } + } } }