feat: enforce case-insensitive unique role names per space
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import { eq, and, inArray } from 'drizzle-orm';
|
import { eq, and, inArray } from 'drizzle-orm';
|
||||||
import { getDb, schema } from '../db/index.js';
|
import { getDb, getRawDb, schema } from '../db/index.js';
|
||||||
import { authenticate } from '../utils/auth.js';
|
import { authenticate } from '../utils/auth.js';
|
||||||
import { generateSnowflake } from '../utils/snowflake.js';
|
import { generateSnowflake } from '../utils/snowflake.js';
|
||||||
import { isMember, isSpaceOwner, isBanned, hasPermission, computePermissions, PermissionBits } from '../utils/permissions.js';
|
import { isMember, isSpaceOwner, isBanned, hasPermission, computePermissions, PermissionBits } from '../utils/permissions.js';
|
||||||
@@ -1010,11 +1010,23 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
permStr = permissionsToString(DEFAULT_EVERYONE_PERMISSIONS);
|
permStr = permissionsToString(DEFAULT_EVERYONE_PERMISSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trim and validate name
|
||||||
|
const roleName = (name || 'new role').trim() || 'new role';
|
||||||
|
|
||||||
|
// Check for case-insensitive duplicate name within the space
|
||||||
|
const rawDb = getRawDb();
|
||||||
|
const duplicate = rawDb.prepare(
|
||||||
|
'SELECT id FROM roles WHERE space_id = ? AND name = ? COLLATE NOCASE'
|
||||||
|
).get(id, roleName);
|
||||||
|
if (duplicate) {
|
||||||
|
return reply.code(409).send({ error: 'A role with this name already exists' });
|
||||||
|
}
|
||||||
|
|
||||||
const roleId = generateSnowflake();
|
const roleId = generateSnowflake();
|
||||||
db.insert(schema.roles).values({
|
db.insert(schema.roles).values({
|
||||||
id: roleId,
|
id: roleId,
|
||||||
spaceId: id,
|
spaceId: id,
|
||||||
name: name || 'new role',
|
name: roleName,
|
||||||
color: color || '#b9bbbe',
|
color: color || '#b9bbbe',
|
||||||
position: 0,
|
position: 0,
|
||||||
permissions: permStr,
|
permissions: permStr,
|
||||||
@@ -1046,7 +1058,21 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updates: Partial<typeof schema.roles.$inferInsert> = {};
|
const updates: Partial<typeof schema.roles.$inferInsert> = {};
|
||||||
if (name !== undefined) updates.name = name;
|
if (name !== undefined) {
|
||||||
|
const trimmed = name.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return reply.code(400).send({ error: 'Role name cannot be empty' });
|
||||||
|
}
|
||||||
|
// Check for case-insensitive duplicate name within the space
|
||||||
|
const rawDb = getRawDb();
|
||||||
|
const duplicate = rawDb.prepare(
|
||||||
|
'SELECT id FROM roles WHERE space_id = ? AND name = ? COLLATE NOCASE AND id != ?'
|
||||||
|
).get(id, trimmed, roleId);
|
||||||
|
if (duplicate) {
|
||||||
|
return reply.code(409).send({ error: 'A role with this name already exists' });
|
||||||
|
}
|
||||||
|
updates.name = trimmed;
|
||||||
|
}
|
||||||
if (color !== undefined) updates.color = color;
|
if (color !== undefined) updates.color = color;
|
||||||
if (position !== undefined) updates.position = position;
|
if (position !== undefined) updates.position = position;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user