feat: Add initial Discord clone application with Convex backend services and Electron React frontend components.

This commit is contained in:
Bryan1029384756
2026-02-10 05:27:10 -06:00
parent 47f173c79b
commit 34e9790db9
29 changed files with 3254 additions and 1398 deletions

View File

@@ -1,5 +1,30 @@
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",
"create_invite",
"embed_links",
"attach_files",
] as const;
async function getRolesForUser(
ctx: GenericQueryCtx<DataModel>,
userId: Id<"userProfiles">
): Promise<Doc<"roles">[]> {
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({
@@ -49,18 +74,17 @@ export const update = mutation({
const role = await ctx.db.get(args.id);
if (!role) throw new Error("Role not found");
const { id, ...fields } = args;
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);
for (const [key, value] of Object.entries(fields)) {
if (value !== undefined) updates[key] = value;
}
return await ctx.db.get(args.id);
if (Object.keys(updates).length > 0) {
await ctx.db.patch(id, updates);
}
return await ctx.db.get(id);
},
});
@@ -72,7 +96,6 @@ export const remove = mutation({
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))
@@ -93,30 +116,14 @@ export const listMembers = query({
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 await Promise.all(
users.map(async (user) => ({
id: user._id,
username: user.username,
public_identity_key: user.publicIdentityKey,
roles: await getRolesForUser(ctx, user._id),
}))
);
return result;
},
});
@@ -128,7 +135,6 @@ export const assign = mutation({
},
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) =>
@@ -181,30 +187,21 @@ export const getMyPermissions = query({
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 roles = await getRolesForUser(ctx, args.userId);
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;
const finalPerms: Record<string, boolean> = {};
for (const key of PERMISSION_KEYS) {
finalPerms[key] = roles.some(
(role) => (role.permissions as Record<string, boolean>)?.[key]
);
}
return finalPerms;
return finalPerms as {
manage_channels: boolean;
manage_roles: boolean;
create_invite: boolean;
embed_links: boolean;
attach_files: boolean;
};
},
});