Skip to content

Commit

Permalink
ALCS-2353 Constraints end error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
fbarreta committed Nov 6, 2024
1 parent 50be39c commit 9c0038c
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ <h4>{{ isEdit ? 'Edit' : 'Create New' }} Category</h4>
<div class="full-width">
<mat-form-field class="description" appearance="outline">
<mat-label>Name</mat-label>
<input required matInput id="name" [(ngModel)]="name" name="name" (ngModelChange)="onChange()" />
<input required matInput id="name" [(ngModel)]="name" name="name" (ngModelChange)="onChange()" [formControl]="nameControl" />
</mat-form-field>
<div class="warning-section">
<div class="warning" *ngIf="showNameWarning">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { TagCategoryDto } from '../../../../../services/tag/tag-category/tag-category.dto';
import { TagCategoryService } from '../../../../../services/tag/tag-category/tag-category.service';
import { FormControl } from '@angular/forms';

@Component({
selector: 'app-tag-category-dialog',
Expand All @@ -15,6 +16,7 @@ export class TagCategoryDialogComponent {
isLoading = false;
isEdit = false;
showNameWarning = false;
nameControl = new FormControl();

constructor(
@Inject(MAT_DIALOG_DATA) public data: TagCategoryDto | undefined,
Expand Down Expand Up @@ -63,6 +65,6 @@ export class TagCategoryDialogComponent {

private showWarning() {
this.showNameWarning = true;
this.name = '';
this.nameControl.setErrors({"invalid": true});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h4>{{ isEdit ? 'Edit' : 'Create New' }} Tag</h4>
<div>
<mat-form-field class="dialog-field" appearance="outline">
<mat-label>Name</mat-label>
<input required matInput id="name" [(ngModel)]="name" name="name" (ngModelChange)="onChange()" />
<input required matInput id="name" [(ngModel)]="name" name="name" (ngModelChange)="onChange()" [formControl]="nameControl" />
</mat-form-field>
</div>
<div class="dialog-field">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TagService } from '../../../../services/tag/tag.service';
import { TagCategoryService } from '../../../../services/tag/tag-category/tag-category.service';
import { TagCategoryDto } from 'src/app/services/tag/tag-category/tag-category.dto';
import { Subject, takeUntil } from 'rxjs';
import { FormControl } from '@angular/forms';

@Component({
selector: 'app-tag-dialog',
Expand All @@ -25,6 +26,7 @@ export class TagDialogComponent implements OnInit {
isLoading = false;
isEdit = false;
showNameWarning = false;
nameControl = new FormControl();

categories: TagCategoryDto[] = [];

Expand Down Expand Up @@ -97,6 +99,6 @@ export class TagDialogComponent implements OnInit {

private showWarning() {
this.showNameWarning = true;
this.name = '';
this.nameControl.setErrors({"invalid": true});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export class TagCategoryService {
return await firstValueFrom(this.http.post<TagCategoryDto>(`${this.url}`, createDto));
} catch (e) {
const res = e as HttpErrorResponse;
if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('duplicate key')) {
throw e as HttpErrorResponse;
if (res.error.statusCode === HttpStatusCode.Conflict) {
throw res;
} else {
console.error(e);
this.toastService.showErrorToast('Failed to create tag category');
Expand All @@ -65,8 +65,8 @@ export class TagCategoryService {
return await firstValueFrom(this.http.patch<TagCategoryDto>(`${this.url}/${uuid}`, updateDto));
} catch (e) {
const res = e as HttpErrorResponse;
if (res.error.statusCode === HttpStatusCode.Conflict && res.error.message.includes('duplicate key')) {
throw e as HttpErrorResponse;
if (res.error.statusCode === HttpStatusCode.Conflict) {
throw res;
} else {
console.error(e);
this.toastService.showErrorToast('Failed to update tag category');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { ApiOAuth2 } from '@nestjs/swagger';
import * as config from 'config';
import { RolesGuard } from '../../../common/authorization/roles-guard.service';
import { UserRoles } from '../../../common/authorization/roles.decorator';
import { AUTH_ROLE } from '../../../common/authorization/roles';
import { TagCategoryDto } from './tag-category.dto';
import { TagCategoryService } from './tag-category.service';
import { QueryFailedError } from 'typeorm';

@Controller('tag-category')
@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES'))
Expand All @@ -40,45 +27,18 @@ export class TagCategoryController {
@Post('')
@UserRoles(AUTH_ROLE.ADMIN)
async create(@Body() createDto: TagCategoryDto) {
try {
return await this.service.create(createDto);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.create(createDto);
}

@Patch('/:uuid')
@UserRoles(AUTH_ROLE.ADMIN)
async update(@Param('uuid') uuid: string, @Body() updateDto: TagCategoryDto) {
try {
return await this.service.update(uuid, updateDto);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.update(uuid, updateDto);
}

@Delete('/:uuid')
@UserRoles(AUTH_ROLE.ADMIN)
async delete(@Param('uuid') uuid: string) {
try {
return await this.service.delete(uuid);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.delete(uuid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export class TagCategory extends Base {
uuid: string;

@AutoMap()
@Column({ unique: true })
@Column()
name: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export class TagCategoryService {
}

async create(dto: TagCategoryDto) {
if (await this.hasName(dto)) {
throw new ServiceConflictException('There is already a category with this name. Unable to create.');
}
const newTagCategory = new TagCategory();
newTagCategory.name = dto.name;
return this.repository.save(newTagCategory);
Expand All @@ -47,6 +50,10 @@ export class TagCategoryService {
}

async update(uuid: string, updateDto: TagCategoryDto) {
updateDto.uuid = uuid;
if (await this.hasName(updateDto)) {
throw new ServiceConflictException('There is already a category with this name. Unable to update.');
}
const tagCategory = await this.getOneOrFail(uuid);
tagCategory.name = updateDto.name;
return await this.repository.save(tagCategory);
Expand All @@ -58,12 +65,22 @@ export class TagCategoryService {
if (await this.isAssociated(tagCategory)) {
throw new ServiceConflictException('Category is associated with tags. Unable to delete.');
}
return await this.repository.remove(tagCategory);
return await this.repository.softDelete(uuid);
}

async isAssociated(tagCategory: TagCategory) {
const associatedTags = await this.tagRepository.find({ where: { category: { uuid: tagCategory.uuid } } });

return associatedTags.length > 0;
}

async hasName(tag: TagCategoryDto) {
let tags = await this.repository.find({
where: { name: tag.name },
});
if (tag.uuid) {
tags = tags.filter((t) => t.uuid !== tag.uuid);
}
return tags.length > 0;
}
}
48 changes: 4 additions & 44 deletions services/apps/alcs/src/alcs/tag/tag.controller.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Param,
Patch,
Post,
Query,
UseGuards,
} from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { ApiOAuth2 } from '@nestjs/swagger';
import * as config from 'config';
import { RolesGuard } from '../../common/authorization/roles-guard.service';
import { UserRoles } from '../../common/authorization/roles.decorator';
import { TagService } from './tag.service';
import { AUTH_ROLE } from '../../common/authorization/roles';
import { TagDto } from './tag.dto';
import { QueryFailedError } from 'typeorm';

@Controller('tag')
@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES'))
Expand All @@ -40,45 +27,18 @@ export class TagController {
@Post('')
@UserRoles(AUTH_ROLE.ADMIN)
async create(@Body() createDto: TagDto) {
try {
return await this.service.create(createDto);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.create(createDto);
}

@Patch('/:uuid')
@UserRoles(AUTH_ROLE.ADMIN)
async update(@Param('uuid') uuid: string, @Body() updateDto: TagDto) {
try {
return await this.service.update(uuid, updateDto);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.update(uuid, updateDto);
}

@Delete('/:uuid')
@UserRoles(AUTH_ROLE.ADMIN)
async delete(@Param('uuid') uuid: string) {
try {
return await this.service.delete(uuid);
} catch (e) {
if (e.constructor === QueryFailedError) {
const msg = (e as QueryFailedError).message;
throw new HttpException(msg, HttpStatus.CONFLICT);
} else {
throw e;
}
}
return await this.service.delete(uuid);
}
}
3 changes: 3 additions & 0 deletions services/apps/alcs/src/alcs/tag/tag.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { IsBoolean, IsObject, IsOptional, IsString } from 'class-validator';
import { TagCategoryDto } from './tag-category/tag-category.dto';

export class TagDto {
@IsString()
uuid: string;

@IsString()
name: string;

Expand Down
4 changes: 2 additions & 2 deletions services/apps/alcs/src/alcs/tag/tag.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class Tag extends Base {
uuid: string;

@AutoMap()
@Column({ unique: true })
@Column()
name: string;

@AutoMap()
Expand All @@ -33,7 +33,7 @@ export class Tag extends Base {

@ManyToMany(() => Application, (application) => application.tags)
applications: Application[];

@ManyToMany(() => NoticeOfIntent, (noticeOfIntent) => noticeOfIntent.tags)
noticeOfIntents: NoticeOfIntent[];
}
17 changes: 17 additions & 0 deletions services/apps/alcs/src/alcs/tag/tag.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export class TagService {
}

async create(dto: TagDto) {
if (await this.hasName(dto)) {
throw new ServiceConflictException('There is already a tag with this name. Unable to create.');
}
const category = dto.category
? await this.categoryRepository.findOne({
where: {
Expand All @@ -60,6 +63,10 @@ export class TagService {
}

async update(uuid: string, updateDto: TagDto) {
updateDto.uuid = uuid;
if (await this.hasName(updateDto)) {
throw new ServiceConflictException('There is already a tag with this name. Unable to update.');
}
const category = updateDto.category
? await this.categoryRepository.findOne({
where: {
Expand Down Expand Up @@ -93,4 +100,14 @@ export class TagService {

return (tag.applications && tag.applications.length > 0) || (tag.noticeOfIntents && tag.noticeOfIntents.length > 0);
}

async hasName(tag: TagDto) {
let tags = await this.repository.find({
where: { name: tag.name },
});
if (tag.uuid) {
tags = tags.filter((t) => t.uuid !== tag.uuid);
}
return tags.length > 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class RemoveUniqueConstraints1730856145155 implements MigrationInterface {
name = 'RemoveUniqueConstraints1730856145155'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "alcs"."tag_category" DROP CONSTRAINT "UQ_f48a9fe1f705a7c2a60856d395a"`);
await queryRunner.query(`ALTER TABLE "alcs"."tag" DROP CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b"`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "alcs"."tag" ADD CONSTRAINT "UQ_6a9775008add570dc3e5a0bab7b" UNIQUE ("name")`);
await queryRunner.query(`ALTER TABLE "alcs"."tag_category" ADD CONSTRAINT "UQ_f48a9fe1f705a7c2a60856d395a" UNIQUE ("name")`);
}

}

0 comments on commit 9c0038c

Please sign in to comment.