Enable event invite actions and add photo print sizes
This commit is contained in:
@@ -182,7 +182,7 @@ export function EventPeople({
|
||||
</Button>
|
||||
</form>
|
||||
) : null}
|
||||
{canManage && event.data?.groupId ? <PendingInvites groupId={event.data.groupId} eventId={eventId} /> : null}
|
||||
{canManage && event.data?.groupId ? <PendingInvites groupId={event.data.groupId} eventId={eventId} canRevoke /> : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -57,12 +57,22 @@ async function validateInviteGrants(userId: string, input: {
|
||||
}
|
||||
}
|
||||
|
||||
async function requireInviteManagement(userId: string, invite: typeof invites.$inferSelect) {
|
||||
if (!invite.groupId) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
if (invite.eventId && (!invite.groupRole || invite.groupRole === "member") &&
|
||||
!invite.grantUnlimitedEvents && invite.grantEventLimit == null && !invite.grantComplimentary) {
|
||||
await validateInviteGrants(userId, { groupId: invite.groupId, eventId: invite.eventId, eventRole: invite.eventRole ?? undefined });
|
||||
return;
|
||||
}
|
||||
const { access } = await loadGroupAccess(userId, invite.groupId, await getPlatformRole(userId));
|
||||
requireGroupPermission(access.permissions, GROUP_PERMISSIONS.PEOPLE_MANAGE);
|
||||
}
|
||||
|
||||
export const groupRouter = createTRPCRouter({
|
||||
copyInviteLink: protectedProcedure.input(z.object({ groupId: z.string().uuid(), inviteId: z.string().uuid() })).mutation(async ({ ctx, input }) => {
|
||||
const { access } = await loadGroupAccess(ctx.session.user.id, input.groupId, await getPlatformRole(ctx.session.user.id));
|
||||
requireGroupPermission(access.permissions, GROUP_PERMISSIONS.PEOPLE_MANAGE);
|
||||
const [invite] = await getDb().select().from(invites).where(and(eq(invites.id, input.inviteId), eq(invites.groupId, input.groupId))).limit(1);
|
||||
if (!invite || invite.kind !== "email") throw new TRPCError({ code: "NOT_FOUND" });
|
||||
await requireInviteManagement(ctx.session.user.id, invite);
|
||||
assertInviteRedeemable(invite);
|
||||
await validateInviteGrants(ctx.session.user.id, { groupId: input.groupId, eventId: invite.eventId ?? undefined, eventRole: invite.eventRole ?? undefined, grantUnlimitedEvents: invite.grantUnlimitedEvents, grantEventLimit: invite.grantEventLimit, grantComplimentary: invite.grantComplimentary });
|
||||
if (!invite.tokenEncrypted) throw new TRPCError({ code: "BAD_REQUEST", message: "This older invitation needs an explicit regeneration before its link can be copied." });
|
||||
@@ -75,13 +85,12 @@ export const groupRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
resendInvite: protectedProcedure.input(z.object({ groupId: z.string().uuid(), inviteId: z.string().uuid(), regenerate: z.boolean().default(false) })).mutation(async ({ ctx, input }) => {
|
||||
const { access } = await loadGroupAccess(ctx.session.user.id, input.groupId, await getPlatformRole(ctx.session.user.id));
|
||||
requireGroupPermission(access.permissions, GROUP_PERMISSIONS.PEOPLE_MANAGE);
|
||||
const limit = await consumeRateLimit({ namespace: "invite-resend", identifier: `${input.groupId}:${input.inviteId}`, limit: 1, windowMs: 60000 });
|
||||
if (!limit.allowed) throw new TRPCError({ code: "TOO_MANY_REQUESTS", message: "Wait one minute before resending this invite." });
|
||||
await getDb().transaction(async tx => {
|
||||
const [invite] = await tx.select().from(invites).where(and(eq(invites.id, input.inviteId), eq(invites.groupId, input.groupId))).limit(1).for("update");
|
||||
if (!invite || invite.kind !== "email" || !invite.email) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
await requireInviteManagement(ctx.session.user.id, invite);
|
||||
const limit = await consumeRateLimit({ namespace: "invite-resend", identifier: `${input.groupId}:${input.inviteId}`, limit: 1, windowMs: 60000 });
|
||||
if (!limit.allowed) throw new TRPCError({ code: "TOO_MANY_REQUESTS", message: "Wait one minute before resending this invite." });
|
||||
assertInviteRedeemable(invite);
|
||||
await validateInviteGrants(ctx.session.user.id, { groupId: input.groupId, eventId: invite.eventId ?? undefined, eventRole: invite.eventRole ?? undefined, grantUnlimitedEvents: invite.grantUnlimitedEvents, grantEventLimit: invite.grantEventLimit, grantComplimentary: invite.grantComplimentary });
|
||||
let token: string;
|
||||
@@ -117,9 +126,10 @@ export const groupRouter = createTRPCRouter({
|
||||
revokeInvite: protectedProcedure
|
||||
.input(z.object({ groupId: z.string().uuid(), inviteId: z.string().uuid() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { access } = await loadGroupAccess(ctx.session.user.id, input.groupId, await getPlatformRole(ctx.session.user.id));
|
||||
requireGroupPermission(access.permissions, GROUP_PERMISSIONS.PEOPLE_MANAGE);
|
||||
await getDb().transaction(async tx => {
|
||||
const [target] = await tx.select().from(invites).where(and(eq(invites.id, input.inviteId), eq(invites.groupId, input.groupId))).limit(1).for("update");
|
||||
if (!target) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
await requireInviteManagement(ctx.session.user.id, target);
|
||||
const [invite] = await tx.update(invites).set({ status: "revoked", updatedAt: new Date() })
|
||||
.where(and(eq(invites.id, input.inviteId), eq(invites.groupId, input.groupId), eq(invites.status, "pending")))
|
||||
.returning({ id: invites.id, eventId: invites.eventId });
|
||||
|
||||
@@ -79,6 +79,18 @@ test.skipIf(process.env.POLISH_INTEGRATION !== "1")("invitation isolation, atomi
|
||||
await expect(workspace.resendInvite({ groupId: group!.id, inviteId: resendTarget!.id })).rejects.toThrow("Wait one minute");
|
||||
await workspace.revokeInvite({ groupId: group!.id, inviteId: resendTarget!.id });
|
||||
await expect(workspace.copyInviteLink({ groupId: group!.id, inviteId: resendTarget!.id })).rejects.toThrow();
|
||||
await db.insert(eventMemberships).values({ eventId: event!.id, userId: people[1]!.id, role: "manager" });
|
||||
const eventManager = groupRouter.createCaller(ctx(1));
|
||||
const eventToken = crypto.randomUUID();
|
||||
const [eventInvite] = await db.insert(invites).values({ groupId: group!.id, eventId: event!.id, kind: "email", email: people[1]!.email, groupRole: "member", eventRole: "manager", tokenHash: hashToken(eventToken), tokenEncrypted: encryptInviteToken(eventToken) }).returning();
|
||||
expect(new URL((await eventManager.copyInviteLink({ groupId: group!.id, inviteId: eventInvite!.id })).url).pathname).toBe(`/invitations/${eventToken}`);
|
||||
await eventManager.resendInvite({ groupId: group!.id, inviteId: eventInvite!.id });
|
||||
await db.update(invites).set({ groupRole: "owner" }).where(eq(invites.id, eventInvite!.id));
|
||||
await expect(eventManager.copyInviteLink({ groupId: group!.id, inviteId: eventInvite!.id })).rejects.toThrow();
|
||||
await expect(eventManager.revokeInvite({ groupId: group!.id, inviteId: eventInvite!.id })).rejects.toThrow();
|
||||
await db.update(invites).set({ groupRole: "member" }).where(eq(invites.id, eventInvite!.id));
|
||||
await eventManager.revokeInvite({ groupId: group!.id, inviteId: eventInvite!.id });
|
||||
await db.delete(eventMemberships).where(and(eq(eventMemberships.eventId, event!.id), eq(eventMemberships.userId, people[1]!.id)));
|
||||
await expect(workspace.createCode({ groupId: group!.id, grantUnlimitedEvents: true })).rejects.toThrow("platform administrators");
|
||||
const signs = signsRouter.createCaller(ctx(0));
|
||||
await expect(signsRouter.createCaller(ctx(1)).prepare({ eventId: event!.id })).rejects.toThrow();
|
||||
|
||||
Reference in New Issue
Block a user