Files
DiscordClone/convex/invites.ts

86 lines
2.0 KiB
TypeScript

import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
// Create invite with encrypted payload
export const create = mutation({
args: {
code: v.string(),
encryptedPayload: v.string(),
createdBy: v.id("userProfiles"),
maxUses: v.optional(v.number()),
expiresAt: v.optional(v.number()),
keyVersion: v.number(),
},
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
await ctx.db.insert("invites", {
code: args.code,
encryptedPayload: args.encryptedPayload,
createdBy: args.createdBy,
maxUses: args.maxUses,
uses: 0,
expiresAt: args.expiresAt,
keyVersion: args.keyVersion,
});
return { success: true };
},
});
// Fetch and validate invite (returns encrypted payload)
export const use = query({
args: { code: v.string() },
returns: v.union(
v.object({
encryptedPayload: v.string(),
keyVersion: v.number(),
}),
v.object({ error: v.string() })
),
handler: async (ctx, args) => {
const invite = await ctx.db
.query("invites")
.withIndex("by_code", (q) => q.eq("code", args.code))
.unique();
if (!invite) {
return { error: "Invite not found" };
}
if (invite.expiresAt && Date.now() > invite.expiresAt) {
return { error: "Invite expired" };
}
if (
invite.maxUses !== undefined &&
invite.maxUses !== null &&
invite.uses >= invite.maxUses
) {
return { error: "Invite max uses reached" };
}
return {
encryptedPayload: invite.encryptedPayload,
keyVersion: invite.keyVersion,
};
},
});
// Revoke invite
export const revoke = mutation({
args: { code: v.string() },
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
const invite = await ctx.db
.query("invites")
.withIndex("by_code", (q) => q.eq("code", args.code))
.unique();
if (invite) {
await ctx.db.delete(invite._id);
}
return { success: true };
},
});