Browse Source

Restrict creation of tags to unique names

pull/7635/head
Thomas Kaul 2 weeks ago
parent
commit
b92946ae4c
  1. 16
      apps/api/src/services/tag/tag.service.ts
  2. 6
      libs/ui/src/lib/tags-selector/tags-selector.component.html
  3. 26
      libs/ui/src/lib/tags-selector/tags-selector.component.ts

16
apps/api/src/services/tag/tag.service.ts

@ -10,9 +10,23 @@ export class TagService {
public constructor(private readonly prismaService: PrismaService) {} public constructor(private readonly prismaService: PrismaService) {}
public async createTag(data: Prisma.TagCreateInput) { public async createTag(data: Prisma.TagCreateInput) {
return this.prismaService.tag.create({ try {
return await this.prismaService.tag.create({
data data
}); });
} catch (error) {
if (
error instanceof Prisma.PrismaClientKnownRequestError &&
error.code === 'P2002'
) {
throw new HttpException(
getReasonPhrase(StatusCodes.CONFLICT),
StatusCodes.CONFLICT
);
}
throw error;
}
} }
public async deleteTag(where: Prisma.TagWhereUniqueInput): Promise<Tag> { public async deleteTag(where: Prisma.TagWhereUniqueInput): Promise<Tag> {

6
libs/ui/src/lib/tags-selector/tags-selector.component.html

@ -55,12 +55,12 @@
</mat-option> </mat-option>
} }
@if (hasPermissionToCreateTag && tagInputControl.value) { @if (hasPermissionToCreateTag && tagNameToCreate()) {
<mat-option [value]="tagInputControl.value.trim()"> <mat-option [value]="tagNameToCreate()">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="add-circle-outline" /> <ion-icon class="mr-2" name="add-circle-outline" />
<ng-container i18n>Create</ng-container> "{{ <ng-container i18n>Create</ng-container> "{{
tagInputControl.value.trim() tagNameToCreate()
}}" }}"
</span> </span>
</mat-option> </mat-option>

26
libs/ui/src/lib/tags-selector/tags-selector.component.ts

@ -72,6 +72,7 @@ export class GfTagsSelectorComponent
); );
public readonly separatorKeysCodes: number[] = [COMMA, ENTER]; public readonly separatorKeysCodes: number[] = [COMMA, ENTER];
public readonly tagInputControl = new FormControl(''); public readonly tagInputControl = new FormControl('');
public readonly tagNameToCreate = signal<string | null>(null);
public readonly tagsSelected = signal<SelectedTag[]>([]); public readonly tagsSelected = signal<SelectedTag[]>([]);
private readonly tagInput = private readonly tagInput =
@ -81,7 +82,7 @@ export class GfTagsSelectorComponent
this.tagInputControl.valueChanges this.tagInputControl.valueChanges
.pipe(takeUntilDestroyed()) .pipe(takeUntilDestroyed())
.subscribe((value) => { .subscribe((value) => {
this.filteredOptions.next(this.filterTags(value ?? '')); this.updateFilters(value ?? '');
}); });
addIcons({ addCircleOutline, closeOutline }); addIcons({ addCircleOutline, closeOutline });
@ -179,6 +180,24 @@ export class GfTagsSelectorComponent
}); });
} }
private getTagNameToCreate(query: string = ''): string | null {
const name = query.trim();
if (!name) {
return null;
}
const isTagNameTaken = [
...(this.tagsAvailable ?? []),
...(this.tagsReadOnly ?? []),
...(this.tagsSelected() ?? [])
].some((tag) => {
return tag.name.toLowerCase() === name.toLowerCase();
});
return isTagNameTaken ? null : name;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
private onChange = (_value: SelectedTag[]): void => { private onChange = (_value: SelectedTag[]): void => {
// ControlValueAccessor onChange callback // ControlValueAccessor onChange callback
@ -188,7 +207,8 @@ export class GfTagsSelectorComponent
// ControlValueAccessor onTouched callback // ControlValueAccessor onTouched callback
}; };
private updateFilters() { private updateFilters(query: string = '') {
this.filteredOptions.next(this.filterTags()); this.filteredOptions.next(this.filterTags(query));
this.tagNameToCreate.set(this.getTagNameToCreate(query));
} }
} }

Loading…
Cancel
Save