feat: implement invite onboarding and dynamic ticket fields
This commit is contained in:
parent
29a647f6c6
commit
f24a7f68ca
34 changed files with 2240 additions and 97 deletions
115
web/convex/invites.ts
Normal file
115
web/convex/invites.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import { mutation, query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
|
||||
import { requireAdmin } from "./rbac";
|
||||
|
||||
export const list = query({
|
||||
args: { tenantId: v.string(), viewerId: v.id("users") },
|
||||
handler: async (ctx, { tenantId, viewerId }) => {
|
||||
await requireAdmin(ctx, viewerId, tenantId);
|
||||
|
||||
const invites = await ctx.db
|
||||
.query("userInvites")
|
||||
.withIndex("by_tenant", (q) => q.eq("tenantId", tenantId))
|
||||
.collect();
|
||||
|
||||
return invites
|
||||
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
|
||||
.map((invite) => ({
|
||||
id: invite._id,
|
||||
inviteId: invite.inviteId,
|
||||
email: invite.email,
|
||||
name: invite.name ?? null,
|
||||
role: invite.role,
|
||||
status: invite.status,
|
||||
token: invite.token,
|
||||
expiresAt: invite.expiresAt,
|
||||
createdAt: invite.createdAt,
|
||||
createdById: invite.createdById ?? null,
|
||||
acceptedAt: invite.acceptedAt ?? null,
|
||||
acceptedById: invite.acceptedById ?? null,
|
||||
revokedAt: invite.revokedAt ?? null,
|
||||
revokedById: invite.revokedById ?? null,
|
||||
revokedReason: invite.revokedReason ?? null,
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
export const sync = mutation({
|
||||
args: {
|
||||
tenantId: v.string(),
|
||||
inviteId: v.string(),
|
||||
email: v.string(),
|
||||
name: v.optional(v.string()),
|
||||
role: v.string(),
|
||||
status: v.string(),
|
||||
token: v.string(),
|
||||
expiresAt: v.number(),
|
||||
createdAt: v.number(),
|
||||
createdById: v.optional(v.string()),
|
||||
acceptedAt: v.optional(v.number()),
|
||||
acceptedById: v.optional(v.string()),
|
||||
revokedAt: v.optional(v.number()),
|
||||
revokedById: v.optional(v.string()),
|
||||
revokedReason: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const existing = await ctx.db
|
||||
.query("userInvites")
|
||||
.withIndex("by_invite", (q) => q.eq("tenantId", args.tenantId).eq("inviteId", args.inviteId))
|
||||
.first();
|
||||
|
||||
if (!existing) {
|
||||
const id = await ctx.db.insert("userInvites", {
|
||||
tenantId: args.tenantId,
|
||||
inviteId: args.inviteId,
|
||||
email: args.email,
|
||||
name: args.name,
|
||||
role: args.role,
|
||||
status: args.status,
|
||||
token: args.token,
|
||||
expiresAt: args.expiresAt,
|
||||
createdAt: args.createdAt,
|
||||
createdById: args.createdById,
|
||||
acceptedAt: args.acceptedAt,
|
||||
acceptedById: args.acceptedById,
|
||||
revokedAt: args.revokedAt,
|
||||
revokedById: args.revokedById,
|
||||
revokedReason: args.revokedReason,
|
||||
});
|
||||
return await ctx.db.get(id);
|
||||
}
|
||||
|
||||
await ctx.db.patch(existing._id, {
|
||||
email: args.email,
|
||||
name: args.name,
|
||||
role: args.role,
|
||||
status: args.status,
|
||||
token: args.token,
|
||||
expiresAt: args.expiresAt,
|
||||
createdAt: args.createdAt,
|
||||
createdById: args.createdById,
|
||||
acceptedAt: args.acceptedAt,
|
||||
acceptedById: args.acceptedById,
|
||||
revokedAt: args.revokedAt,
|
||||
revokedById: args.revokedById,
|
||||
revokedReason: args.revokedReason,
|
||||
});
|
||||
|
||||
return await ctx.db.get(existing._id);
|
||||
},
|
||||
});
|
||||
|
||||
export const remove = mutation({
|
||||
args: { tenantId: v.string(), inviteId: v.string() },
|
||||
handler: async (ctx, { tenantId, inviteId }) => {
|
||||
const existing = await ctx.db
|
||||
.query("userInvites")
|
||||
.withIndex("by_invite", (q) => q.eq("tenantId", tenantId).eq("inviteId", inviteId))
|
||||
.first();
|
||||
|
||||
if (existing) {
|
||||
await ctx.db.delete(existing._id);
|
||||
}
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue