Version Bump 1.0.40
All checks were successful
Build and Release / build-and-release (push) Successful in 19m24s
All checks were successful
Build and Release / build-and-release (push) Successful in 19m24s
This commit is contained in:
145
convex/auth.ts
145
convex/auth.ts
@@ -367,6 +367,151 @@ export const setNickname = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
// Delete a user and all their associated data (admin only)
|
||||
export const deleteUser = mutation({
|
||||
args: {
|
||||
requestingUserId: v.id("userProfiles"),
|
||||
targetUserId: v.id("userProfiles"),
|
||||
},
|
||||
returns: v.object({ success: v.boolean(), error: v.optional(v.string()) }),
|
||||
handler: async (ctx, args) => {
|
||||
// Verify requesting user is admin
|
||||
const requester = await ctx.db.get(args.requestingUserId);
|
||||
if (!requester || !requester.isAdmin) {
|
||||
return { success: false, error: "Only admins can delete users" };
|
||||
}
|
||||
|
||||
// Prevent self-deletion
|
||||
if (args.requestingUserId === args.targetUserId) {
|
||||
return { success: false, error: "Cannot delete your own account" };
|
||||
}
|
||||
|
||||
const target = await ctx.db.get(args.targetUserId);
|
||||
if (!target) {
|
||||
return { success: false, error: "User not found" };
|
||||
}
|
||||
|
||||
// Prevent deleting other admins
|
||||
if (target.isAdmin) {
|
||||
return { success: false, error: "Cannot delete another admin" };
|
||||
}
|
||||
|
||||
// Delete reactions made by this user (before messages, using index)
|
||||
const userReactions = await ctx.db
|
||||
.query("messageReactions")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const r of userReactions) {
|
||||
await ctx.db.delete(r._id);
|
||||
}
|
||||
|
||||
// Delete all messages by this user (using index)
|
||||
const messages = await ctx.db
|
||||
.query("messages")
|
||||
.withIndex("by_sender", (q) => q.eq("senderId", args.targetUserId))
|
||||
.collect();
|
||||
for (const msg of messages) {
|
||||
// Delete reactions on this message
|
||||
const reactions = await ctx.db
|
||||
.query("messageReactions")
|
||||
.withIndex("by_message", (q) => q.eq("messageId", msg._id))
|
||||
.collect();
|
||||
for (const r of reactions) {
|
||||
await ctx.db.delete(r._id);
|
||||
}
|
||||
await ctx.db.delete(msg._id);
|
||||
}
|
||||
|
||||
// Delete channel keys
|
||||
const channelKeys = await ctx.db
|
||||
.query("channelKeys")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const ck of channelKeys) {
|
||||
await ctx.db.delete(ck._id);
|
||||
}
|
||||
|
||||
// Delete role assignments
|
||||
const userRoles = await ctx.db
|
||||
.query("userRoles")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const ur of userRoles) {
|
||||
await ctx.db.delete(ur._id);
|
||||
}
|
||||
|
||||
// Delete DM participations
|
||||
const dmParts = await ctx.db
|
||||
.query("dmParticipants")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const dp of dmParts) {
|
||||
await ctx.db.delete(dp._id);
|
||||
}
|
||||
|
||||
// Delete typing indicators
|
||||
const typingIndicators = await ctx.db
|
||||
.query("typingIndicators")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const ti of typingIndicators) {
|
||||
await ctx.db.delete(ti._id);
|
||||
}
|
||||
|
||||
// Delete voice states
|
||||
const voiceStates = await ctx.db
|
||||
.query("voiceStates")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const vs of voiceStates) {
|
||||
await ctx.db.delete(vs._id);
|
||||
}
|
||||
|
||||
// Delete read states
|
||||
const readStates = await ctx.db
|
||||
.query("channelReadState")
|
||||
.withIndex("by_user", (q) => q.eq("userId", args.targetUserId))
|
||||
.collect();
|
||||
for (const rs of readStates) {
|
||||
await ctx.db.delete(rs._id);
|
||||
}
|
||||
|
||||
// Delete invites created by this user
|
||||
const invites = await ctx.db
|
||||
.query("invites")
|
||||
.withIndex("by_creator", (q) => q.eq("createdBy", args.targetUserId))
|
||||
.collect();
|
||||
for (const inv of invites) {
|
||||
await ctx.db.delete(inv._id);
|
||||
}
|
||||
|
||||
// Delete custom emojis uploaded by this user
|
||||
const emojis = await ctx.db
|
||||
.query("customEmojis")
|
||||
.withIndex("by_uploader", (q) => q.eq("uploadedBy", args.targetUserId))
|
||||
.collect();
|
||||
for (const emoji of emojis) {
|
||||
await ctx.storage.delete(emoji.storageId);
|
||||
await ctx.db.delete(emoji._id);
|
||||
}
|
||||
|
||||
// Delete avatar from storage if exists
|
||||
if (target.avatarStorageId) {
|
||||
await ctx.storage.delete(target.avatarStorageId);
|
||||
}
|
||||
|
||||
// Delete join sound from storage if exists
|
||||
if (target.joinSoundStorageId) {
|
||||
await ctx.storage.delete(target.joinSoundStorageId);
|
||||
}
|
||||
|
||||
// Delete the user profile
|
||||
await ctx.db.delete(args.targetUserId);
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
// Internal: update credentials after password reset
|
||||
export const updateCredentials = internalMutation({
|
||||
args: {
|
||||
|
||||
Reference in New Issue
Block a user