import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; // List all roles export const list = query({ args: {}, returns: v.array(v.any()), handler: async (ctx) => { const roles = await ctx.db.query("roles").collect(); return roles.sort((a, b) => (b.position || 0) - (a.position || 0)); }, }); // Create new role export const create = mutation({ args: { name: v.optional(v.string()), color: v.optional(v.string()), permissions: v.optional(v.any()), position: v.optional(v.number()), isHoist: v.optional(v.boolean()), }, returns: v.any(), handler: async (ctx, args) => { const id = await ctx.db.insert("roles", { name: args.name || "new role", color: args.color || "#99aab5", position: args.position || 0, permissions: args.permissions || {}, isHoist: args.isHoist || false, }); return await ctx.db.get(id); }, }); // Update role properties export const update = mutation({ args: { id: v.id("roles"), name: v.optional(v.string()), color: v.optional(v.string()), permissions: v.optional(v.any()), position: v.optional(v.number()), isHoist: v.optional(v.boolean()), }, returns: v.any(), handler: async (ctx, args) => { const role = await ctx.db.get(args.id); if (!role) throw new Error("Role not found"); const updates: Record = {}; if (args.name !== undefined) updates.name = args.name; if (args.color !== undefined) updates.color = args.color; if (args.permissions !== undefined) updates.permissions = args.permissions; if (args.position !== undefined) updates.position = args.position; if (args.isHoist !== undefined) updates.isHoist = args.isHoist; if (Object.keys(updates).length > 0) { await ctx.db.patch(args.id, updates); } return await ctx.db.get(args.id); }, }); // Delete role export const remove = mutation({ args: { id: v.id("roles") }, returns: v.object({ success: v.boolean() }), handler: async (ctx, args) => { const role = await ctx.db.get(args.id); if (!role) throw new Error("Role not found"); // Delete user_role assignments const assignments = await ctx.db .query("userRoles") .withIndex("by_role", (q) => q.eq("roleId", args.id)) .collect(); for (const a of assignments) { await ctx.db.delete(a._id); } await ctx.db.delete(args.id); return { success: true }; }, }); // List members with roles export const listMembers = query({ args: {}, returns: v.array(v.any()), handler: async (ctx) => { const users = await ctx.db.query("userProfiles").collect(); const result = await Promise.all( users.map(async (user) => { const userRoleAssignments = await ctx.db .query("userRoles") .withIndex("by_user", (q) => q.eq("userId", user._id)) .collect(); const roles = await Promise.all( userRoleAssignments.map(async (ur) => { const role = await ctx.db.get(ur.roleId); return role; }) ); return { id: user._id, username: user.username, public_identity_key: user.publicIdentityKey, roles: roles.filter(Boolean), }; }) ); return result; }, }); // Assign role to user export const assign = mutation({ args: { roleId: v.id("roles"), userId: v.id("userProfiles"), }, returns: v.object({ success: v.boolean() }), handler: async (ctx, args) => { // Check if already assigned const existing = await ctx.db .query("userRoles") .withIndex("by_user_and_role", (q) => q.eq("userId", args.userId).eq("roleId", args.roleId) ) .unique(); if (!existing) { await ctx.db.insert("userRoles", { userId: args.userId, roleId: args.roleId, }); } return { success: true }; }, }); // Remove role from user export const unassign = mutation({ args: { roleId: v.id("roles"), userId: v.id("userProfiles"), }, returns: v.object({ success: v.boolean() }), handler: async (ctx, args) => { const existing = await ctx.db .query("userRoles") .withIndex("by_user_and_role", (q) => q.eq("userId", args.userId).eq("roleId", args.roleId) ) .unique(); if (existing) { await ctx.db.delete(existing._id); } return { success: true }; }, }); // Get current user's aggregated permissions export const getMyPermissions = query({ args: { userId: v.id("userProfiles") }, returns: v.object({ manage_channels: v.boolean(), manage_roles: v.boolean(), create_invite: v.boolean(), embed_links: v.boolean(), attach_files: v.boolean(), }), handler: async (ctx, args) => { const userRoleAssignments = await ctx.db .query("userRoles") .withIndex("by_user", (q) => q.eq("userId", args.userId)) .collect(); const finalPerms = { manage_channels: false, manage_roles: false, create_invite: false, embed_links: false, attach_files: false, }; for (const ur of userRoleAssignments) { const role = await ctx.db.get(ur.roleId); if (!role) continue; const p = (role.permissions || {}) as Record; if (p.manage_channels) finalPerms.manage_channels = true; if (p.manage_roles) finalPerms.manage_roles = true; if (p.create_invite) finalPerms.create_invite = true; if (p.embed_links) finalPerms.embed_links = true; if (p.attach_files) finalPerms.attach_files = true; } return finalPerms; }, });