This commit is contained in:
Bryan1029384756
2026-02-18 10:16:12 -06:00
parent ce9902d95d
commit ff269ee154
19 changed files with 583 additions and 64 deletions

View File

@@ -1,6 +1,7 @@
import { query, mutation, internalQuery, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { getPublicStorageUrl } from "./storageUrl";
import { getRolesForUser } from "./roles";
async function sha256Hex(input: string): Promise<string> {
const buffer = await crypto.subtle.digest(
@@ -165,6 +166,7 @@ export const createUserWithProfile = mutation({
manage_channels: true,
manage_roles: true,
manage_messages: true,
manage_nicknames: true,
create_invite: true,
embed_links: true,
attach_files: true,
@@ -337,6 +339,34 @@ export const getUserForRecovery = internalQuery({
},
});
// Set nickname (displayName) for a user
export const setNickname = mutation({
args: {
actorUserId: v.id("userProfiles"),
targetUserId: v.id("userProfiles"),
displayName: v.string(),
},
returns: v.null(),
handler: async (ctx, args) => {
// Self-changes are always allowed
if (args.actorUserId !== args.targetUserId) {
const roles = await getRolesForUser(ctx, args.actorUserId);
const canManage = roles.some(
(role) => (role.permissions as Record<string, boolean>)?.["manage_nicknames"]
);
if (!canManage) {
throw new Error("You don't have permission to change other users' nicknames");
}
}
const trimmed = args.displayName.trim();
await ctx.db.patch(args.targetUserId, {
displayName: trimmed || undefined,
});
return null;
},
});
// Internal: update credentials after password reset
export const updateCredentials = internalMutation({
args: {

View File

@@ -50,6 +50,7 @@ export const listDMs = query({
channel_name: v.string(),
other_user_id: v.string(),
other_username: v.string(),
other_displayName: v.union(v.string(), v.null()),
other_user_status: v.optional(v.string()),
other_user_avatar_url: v.optional(v.union(v.string(), v.null())),
})
@@ -85,6 +86,7 @@ export const listDMs = query({
channel_name: channel.name,
other_user_id: otherUser._id as string,
other_username: otherUser.username,
other_displayName: otherUser.displayName || null,
other_user_status: otherUser.status || "offline",
other_user_avatar_url: avatarUrl,
};

View File

@@ -51,6 +51,7 @@ export const getChannelMembers = query({
members.push({
id: user._id,
username: user.username,
displayName: user.displayName || null,
status: user.status || "offline",
roles: roles.sort((a, b) => b.position - a.position),
avatarUrl,
@@ -77,6 +78,7 @@ export const listAll = query({
results.push({
id: user._id,
username: user.username,
displayName: user.displayName || null,
status: user.status || "offline",
avatarUrl,
});

View File

@@ -27,6 +27,7 @@ async function enrichMessage(ctx: any, msg: any, userId?: any) {
}
let replyToUsername: string | null = null;
let replyToDisplayName: string | null = null;
let replyToContent: string | null = null;
let replyToNonce: string | null = null;
let replyToAvatarUrl: string | null = null;
@@ -35,6 +36,7 @@ async function enrichMessage(ctx: any, msg: any, userId?: any) {
if (repliedMsg) {
const repliedSender = await ctx.db.get(repliedMsg.senderId);
replyToUsername = repliedSender?.username || "Unknown";
replyToDisplayName = repliedSender?.displayName || null;
replyToContent = repliedMsg.ciphertext;
replyToNonce = repliedMsg.nonce;
if (repliedSender?.avatarStorageId) {
@@ -53,11 +55,13 @@ async function enrichMessage(ctx: any, msg: any, userId?: any) {
key_version: msg.keyVersion,
created_at: new Date(msg._creationTime).toISOString(),
username: sender?.username || "Unknown",
displayName: sender?.displayName || null,
public_signing_key: sender?.publicSigningKey || "",
avatarUrl,
reactions: Object.keys(reactions).length > 0 ? reactions : null,
replyToId: msg.replyTo || null,
replyToUsername,
replyToDisplayName,
replyToContent,
replyToNonce,
replyToAvatarUrl,

View File

@@ -12,6 +12,7 @@ const PERMISSION_KEYS = [
"attach_files",
"move_members",
"mute_members",
"manage_nicknames",
] as const;
export async function getRolesForUser(
@@ -191,6 +192,7 @@ export const getMyPermissions = query({
attach_files: v.boolean(),
move_members: v.boolean(),
mute_members: v.boolean(),
manage_nicknames: v.boolean(),
}),
handler: async (ctx, args) => {
const roles = await getRolesForUser(ctx, args.userId);
@@ -211,6 +213,7 @@ export const getMyPermissions = query({
attach_files: boolean;
move_members: boolean;
mute_members: boolean;
manage_nicknames: boolean;
};
},
});

View File

@@ -64,6 +64,7 @@ export const getTyping = query({
v.object({
userId: v.id("userProfiles"),
username: v.string(),
displayName: v.union(v.string(), v.null()),
})
),
handler: async (ctx, args) => {
@@ -73,9 +74,17 @@ export const getTyping = query({
.withIndex("by_channel", (q) => q.eq("channelId", args.channelId))
.collect();
return indicators
.filter((t) => t.expiresAt > now)
.map((t) => ({ userId: t.userId, username: t.username }));
const active = indicators.filter((t) => t.expiresAt > now);
const results = [];
for (const t of active) {
const user = await ctx.db.get(t.userId);
results.push({
userId: t.userId,
username: t.username,
displayName: user?.displayName || null,
});
}
return results;
},
});

View File

@@ -146,6 +146,7 @@ export const getAll = query({
const grouped: Record<string, Array<{
userId: string;
username: string;
displayName: string | null;
isMuted: boolean;
isDeafened: boolean;
isScreenSharing: boolean;
@@ -169,6 +170,7 @@ export const getAll = query({
(grouped[s.channelId] ??= []).push({
userId: s.userId,
username: s.username,
displayName: user?.displayName || null,
isMuted: s.isMuted,
isDeafened: s.isDeafened,
isScreenSharing: s.isScreenSharing,