import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import { GenericQueryCtx } from "convex/server"; import { DataModel, Id, Doc } from "./_generated/dataModel"; const PERMISSION_KEYS = [ "manage_channels", "manage_roles", "manage_messages", "create_invite", "embed_links", "attach_files", "move_members", "mute_members", ] as const; export async function getRolesForUser( ctx: GenericQueryCtx, userId: Id<"userProfiles"> ): Promise[]> { const assignments = await ctx.db .query("userRoles") .withIndex("by_user", (q) => q.eq("userId", userId)) .collect(); const roles = await Promise.all( assignments.map((ur) => ctx.db.get(ur.roleId)) ); return roles.filter((r): r is Doc<"roles"> => r !== null); } // 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 { id, ...fields } = args; const updates: Record = {}; for (const [key, value] of Object.entries(fields)) { if (value !== undefined) updates[key] = value; } if (Object.keys(updates).length > 0) { await ctx.db.patch(id, updates); } return await ctx.db.get(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"); 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(); return await Promise.all( users.map(async (user) => ({ id: user._id, username: user.username, public_identity_key: user.publicIdentityKey, roles: await getRolesForUser(ctx, user._id), })) ); }, }); // 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) => { 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(), manage_messages: v.boolean(), create_invite: v.boolean(), embed_links: v.boolean(), attach_files: v.boolean(), move_members: v.boolean(), mute_members: v.boolean(), }), handler: async (ctx, args) => { const roles = await getRolesForUser(ctx, args.userId); const finalPerms: Record = {}; for (const key of PERMISSION_KEYS) { finalPerms[key] = roles.some( (role) => (role.permissions as Record)?.[key] ); } return finalPerms as { manage_channels: boolean; manage_roles: boolean; manage_messages: boolean; create_invite: boolean; embed_links: boolean; attach_files: boolean; move_members: boolean; mute_members: boolean; }; }, });