Skip to content

Commit

Permalink
move validation to service
Browse files Browse the repository at this point in the history
  • Loading branch information
TIL-EBP committed Nov 6, 2024
1 parent 852ef66 commit d3b50f8
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 89 deletions.
2 changes: 2 additions & 0 deletions apps/server-asset-sg/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -51,6 +52,7 @@ import { WorkgroupsController } from '@/features/workgroups/workgroups.controlle
provideElasticsearch,
AssetEditRepo,
AssetInfoRepo,
AssetEditService,
AssetRepo,
AssetSearchService,
AssetSyncService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown> {
Expand All @@ -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);
Expand All @@ -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) {
Expand Down
43 changes: 0 additions & 43 deletions apps/server-asset-sg/src/features/asset-edit/asset-edit.repo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export class AssetEditRepo implements Repo<AssetEditDetail, number, AssetEditDat
}

async create(data: AssetEditData): Promise<AssetEditDetail> {
await this.validateReferencesOrThrow(data);
const asset = await this.prismaService.asset.create({
select: { assetId: true },
data: {
Expand Down Expand Up @@ -112,9 +111,6 @@ export class AssetEditRepo implements Repo<AssetEditDetail, number, AssetEditDat
if (count === 0) {
return null;
}

await this.validateReferencesOrThrow(data, id);

// Run the update in a transaction, as it consists of multiple prisma queries.
// Note that all mutations within this transaction are no-ops if there is no asset for `id`.
await this.prismaService.$transaction(async () => {
Expand Down Expand Up @@ -320,45 +316,6 @@ export class AssetEditRepo implements Repo<AssetEditDetail, number, AssetEditDat
}
return detailResult.right as AssetEditDetail;
}

private async validateReferencesOrThrow(data: AssetEditData, id?: number): Promise<void> {
// 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');
}
}
}
}
}

/**
Expand Down
92 changes: 47 additions & 45 deletions apps/server-asset-sg/src/features/asset-edit/asset-edit.service.ts
Original file line number Diff line number Diff line change
@@ -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<typeof AssetEditDetail>;
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<void> {
// 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
);
}
}
}
}
}

0 comments on commit d3b50f8

Please sign in to comment.