feat: Implement core Discord clone functionality including Convex backend, Electron frontend, and UI components for chat, voice, and settings.

This commit is contained in:
Bryan1029384756
2026-02-10 04:41:10 -06:00
parent 516cfdbbd8
commit 47f173c79b
63 changed files with 4467 additions and 5292 deletions

210
convex/roles.ts Normal file
View File

@@ -0,0 +1,210 @@
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<string, unknown> = {};
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<string, boolean>;
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;
},
});