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