This commit is contained in:
Philipinho
2026-02-17 02:32:00 +00:00
parent ca173a9c98
commit 75f7f9b296
3 changed files with 20 additions and 10 deletions
@@ -6,8 +6,14 @@ import {
IsOptional, IsOptional,
IsString, IsString,
IsUUID, IsUUID,
Matches,
MaxLength, MaxLength,
} from 'class-validator'; } from 'class-validator';
import { Transform } from 'class-transformer';
function normalizeLabel(name: string): string {
return name.trim().replace(/\s+/g, '-').toLowerCase();
}
export class AddLabelsDto { export class AddLabelsDto {
@IsString() @IsString()
@@ -19,7 +25,14 @@ export class AddLabelsDto {
@ArrayMaxSize(25) @ArrayMaxSize(25)
@IsString({ each: true }) @IsString({ each: true })
@IsNotEmpty({ each: true }) @IsNotEmpty({ each: true })
@Transform(({ value }) =>
Array.isArray(value) ? value.map(normalizeLabel) : value,
)
@MaxLength(100, { each: true }) @MaxLength(100, { each: true })
@Matches(/^[a-z0-9_~-]+$/, {
each: true,
message: 'Label names can only contain letters, numbers, hyphens, underscores, and tildes',
})
names: string[]; names: string[];
} }
@@ -19,9 +19,9 @@ export async function up(db: Kysely<any>): Promise<void> {
.execute(); .execute();
await db.schema await db.schema
.createIndex('labels_workspace_id_lower_name_unique') .createIndex('labels_workspace_id_name_unique')
.on('labels') .on('labels')
.expression(sql`workspace_id, LOWER(name)`) .columns(['workspace_id', 'name'])
.unique() .unique()
.execute(); .execute();
@@ -3,7 +3,6 @@ import { InjectKysely } from 'nestjs-kysely';
import { KyselyDB, KyselyTransaction } from '../../types/kysely.types'; import { KyselyDB, KyselyTransaction } from '../../types/kysely.types';
import { Label } from '@docmost/db/types/entity.types'; import { Label } from '@docmost/db/types/entity.types';
import { dbOrTx } from '@docmost/db/utils'; import { dbOrTx } from '@docmost/db/utils';
import { sql } from 'kysely';
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo'; import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
@Injectable() @Injectable()
@@ -34,7 +33,7 @@ export class LabelRepo {
return db return db
.selectFrom('labels') .selectFrom('labels')
.selectAll() .selectAll()
.where(sql`LOWER(name)`, '=', name.toLowerCase()) .where('name', '=', name.toLowerCase())
.where('workspaceId', '=', workspaceId) .where('workspaceId', '=', workspaceId)
.executeTakeFirst(); .executeTakeFirst();
} }
@@ -45,15 +44,13 @@ export class LabelRepo {
trx?: KyselyTransaction, trx?: KyselyTransaction,
): Promise<Label> { ): Promise<Label> {
const db = dbOrTx(this.db, trx); const db = dbOrTx(this.db, trx);
const trimmedName = name.trim(); const normalizedName = name.trim().toLowerCase();
const result = await db const result = await db
.insertInto('labels') .insertInto('labels')
.values({ name: trimmedName, workspaceId }) .values({ name: normalizedName, workspaceId })
.onConflict((oc) => .onConflict((oc) =>
oc oc.columns(['name', 'workspaceId']).doNothing(),
.expression(sql`workspace_id, LOWER(name)`)
.doNothing(),
) )
.returningAll() .returningAll()
.executeTakeFirst(); .executeTakeFirst();
@@ -62,7 +59,7 @@ export class LabelRepo {
return result; return result;
} }
return this.findByNameAndWorkspace(trimmedName, workspaceId, trx); return this.findByNameAndWorkspace(normalizedName, workspaceId, trx);
} }
async findLabelsByPageId(pageId: string): Promise<Label[]> { async findLabelsByPageId(pageId: string): Promise<Label[]> {