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 async createTag(data: Prisma.TagCreateInput) {
return this.prismaService.tag.create({
try {
return await this.prismaService.tag.create({
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> {

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

@ -55,12 +55,12 @@
</mat-option>
}
@if (hasPermissionToCreateTag && tagInputControl.value) {
<mat-option [value]="tagInputControl.value.trim()">
@if (hasPermissionToCreateTag && tagNameToCreate()) {
<mat-option [value]="tagNameToCreate()">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="add-circle-outline" />
<ng-container i18n>Create</ng-container> "{{
tagInputControl.value.trim()
tagNameToCreate()
}}"
</span>
</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 tagInputControl = new FormControl('');
public readonly tagNameToCreate = signal<string | null>(null);
public readonly tagsSelected = signal<SelectedTag[]>([]);
private readonly tagInput =
@ -81,7 +82,7 @@ export class GfTagsSelectorComponent
this.tagInputControl.valueChanges
.pipe(takeUntilDestroyed())
.subscribe((value) => {
this.filteredOptions.next(this.filterTags(value ?? ''));
this.updateFilters(value ?? '');
});
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
private onChange = (_value: SelectedTag[]): void => {
// ControlValueAccessor onChange callback
@ -188,7 +207,8 @@ export class GfTagsSelectorComponent
// ControlValueAccessor onTouched callback
};
private updateFilters() {
this.filteredOptions.next(this.filterTags());
private updateFilters(query: string = '') {
this.filteredOptions.next(this.filterTags(query));
this.tagNameToCreate.set(this.getTagNameToCreate(query));
}
}

Loading…
Cancel
Save