feat: refactor space settings into panel components with roles management
Restructure SpaceSettings modal from 778-line monolith into thin orchestrator with extracted panel components. Add full role CRUD with permission editor (create, edit name/color/permissions, delete). Backend: expose role permissions in GET response, accept permissions in POST/PATCH role endpoints with BigInt validation, broadcast pushReadyPayload to all space members on role mutations.
This commit is contained in:
@@ -243,6 +243,8 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
const canManageRoles = (spacePerms & PermissionBits.MANAGE_ROLES) !== 0n;
|
||||
|
||||
const result: SpaceWithChannelsAndMembers = {
|
||||
...rowToSpace(server),
|
||||
channels: visibleChannels,
|
||||
@@ -253,6 +255,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
name: r.name,
|
||||
color: r.color ?? '#b9bbbe',
|
||||
position: r.position ?? 0,
|
||||
permissions: canManageRoles ? (r.permissions ?? '0') : undefined,
|
||||
createdAt: r.createdAt,
|
||||
})),
|
||||
myPermissions: permissionsToString(spacePerms),
|
||||
@@ -289,7 +292,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
}
|
||||
|
||||
if (icon !== undefined) {
|
||||
updates.icon = icon;
|
||||
updates.icon = icon || null;
|
||||
}
|
||||
|
||||
if (visibility !== undefined) {
|
||||
@@ -764,17 +767,28 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
// Role Management
|
||||
|
||||
// POST /api/spaces/:id/roles - Create a new role
|
||||
app.post<{ Params: { id: string }; Body: { name: string; color?: string } }>('/api/spaces/:id/roles', {
|
||||
app.post<{ Params: { id: string }; Body: { name: string; color?: string; permissions?: string } }>('/api/spaces/:id/roles', {
|
||||
preHandler: authenticate,
|
||||
}, async (request, reply) => {
|
||||
const { id } = request.params;
|
||||
const { name, color } = request.body;
|
||||
const { name, color, permissions } = request.body;
|
||||
const db = getDb();
|
||||
|
||||
if (!hasPermission(request.userId, id, PermissionBits.MANAGE_ROLES)) {
|
||||
return reply.code(403).send({ error: 'Missing MANAGE_ROLES permission', statusCode: 403 });
|
||||
}
|
||||
|
||||
// Validate permissions string is a valid bigint if provided
|
||||
let permStr: string | undefined;
|
||||
if (permissions !== undefined) {
|
||||
try {
|
||||
BigInt(permissions);
|
||||
permStr = permissions;
|
||||
} catch {
|
||||
return reply.code(400).send({ error: 'Invalid permissions value', statusCode: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
const roleId = generateSnowflake();
|
||||
db.insert(schema.roles).values({
|
||||
id: roleId,
|
||||
@@ -782,27 +796,60 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
name: name || 'new role',
|
||||
color: color || '#b9bbbe',
|
||||
position: 0,
|
||||
permissions: permStr ?? null,
|
||||
createdAt: Date.now(),
|
||||
}).run();
|
||||
|
||||
const role = db.select().from(schema.roles).where(eq(schema.roles.id, roleId)).get();
|
||||
|
||||
// Broadcast updated state to all space members
|
||||
const memberRows = db.select().from(schema.spaceMembers).where(eq(schema.spaceMembers.spaceId, id)).all();
|
||||
for (const m of memberRows) {
|
||||
connectionManager.pushReadyPayload(m.userId);
|
||||
}
|
||||
|
||||
return reply.code(201).send(role);
|
||||
});
|
||||
|
||||
// PATCH /api/spaces/:id/roles/:roleId - Update a role
|
||||
app.patch<{ Params: { id: string; roleId: string }; Body: { name?: string; color?: string; position?: number } }>('/api/spaces/:id/roles/:roleId', {
|
||||
app.patch<{ Params: { id: string; roleId: string }; Body: { name?: string; color?: string; position?: number; permissions?: string } }>('/api/spaces/:id/roles/:roleId', {
|
||||
preHandler: authenticate,
|
||||
}, async (request, reply) => {
|
||||
const { id, roleId } = request.params;
|
||||
const updates = request.body;
|
||||
const { name, color, position, permissions } = request.body;
|
||||
const db = getDb();
|
||||
|
||||
if (!hasPermission(request.userId, id, PermissionBits.MANAGE_ROLES)) {
|
||||
return reply.code(403).send({ error: 'Missing MANAGE_ROLES permission', statusCode: 403 });
|
||||
}
|
||||
|
||||
const updates: Partial<typeof schema.roles.$inferInsert> = {};
|
||||
if (name !== undefined) updates.name = name;
|
||||
if (color !== undefined) updates.color = color;
|
||||
if (position !== undefined) updates.position = position;
|
||||
|
||||
if (permissions !== undefined) {
|
||||
try {
|
||||
BigInt(permissions);
|
||||
updates.permissions = permissions;
|
||||
} catch {
|
||||
return reply.code(400).send({ error: 'Invalid permissions value', statusCode: 400 });
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return reply.code(400).send({ error: 'No fields to update', statusCode: 400 });
|
||||
}
|
||||
|
||||
db.update(schema.roles).set(updates).where(and(eq(schema.roles.id, roleId), eq(schema.roles.spaceId, id))).run();
|
||||
const updated = db.select().from(schema.roles).where(eq(schema.roles.id, roleId)).get();
|
||||
|
||||
// Broadcast updated state to all space members
|
||||
const memberRows = db.select().from(schema.spaceMembers).where(eq(schema.spaceMembers.spaceId, id)).all();
|
||||
for (const m of memberRows) {
|
||||
connectionManager.pushReadyPayload(m.userId);
|
||||
}
|
||||
|
||||
return reply.code(200).send(updated);
|
||||
});
|
||||
|
||||
@@ -828,6 +875,13 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
).run();
|
||||
|
||||
db.delete(schema.roles).where(and(eq(schema.roles.id, roleId), eq(schema.roles.spaceId, id))).run();
|
||||
|
||||
// Broadcast updated state to all space members
|
||||
const memberRows = db.select().from(schema.spaceMembers).where(eq(schema.spaceMembers.spaceId, id)).all();
|
||||
for (const m of memberRows) {
|
||||
connectionManager.pushReadyPayload(m.userId);
|
||||
}
|
||||
|
||||
return reply.code(200).send({ success: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user