Skip to content

Commit 550d61e

Browse files
authored
Multi-user/multi-ticket creation API (#265)
1 parent 86804ca commit 550d61e

6 files changed

Lines changed: 127 additions & 53 deletions

File tree

src/generated/schema.gql

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,9 @@ input GeneratePaymentLinkInput {
277277

278278
input GiftTicketsToUserInput {
279279
allowMultipleTicketsPerUsers: Boolean!
280+
autoApproveTickets: Boolean!
280281
notifyUsers: Boolean!
281-
ticketId: String!
282+
ticketIds: [String!]!
282283
userIds: [String!]!
283284
}
284285

@@ -1261,8 +1262,15 @@ type WorkSeniority {
12611262
}
12621263

12631264
input placeHolderUsersInput {
1265+
ciudad: String
12641266
email: String!
1267+
emergencyPhoneNumber: String
1268+
foodAllergies: String
12651269
name: String!
1270+
nombreOrganizacion: String
1271+
pais: String
1272+
rolEnOrganizacion: String
1273+
trabajasEnOrganizacion: String
12661274
}
12671275

12681276
input updateUserDataInput {

src/generated/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,9 @@ export type GeneratePaymentLinkInput = {
295295

296296
export type GiftTicketsToUserInput = {
297297
allowMultipleTicketsPerUsers: Scalars["Boolean"]["input"];
298+
autoApproveTickets: Scalars["Boolean"]["input"];
298299
notifyUsers: Scalars["Boolean"]["input"];
299-
ticketId: Scalars["String"]["input"];
300+
ticketIds: Array<Scalars["String"]["input"]>;
300301
userIds: Array<Scalars["String"]["input"]>;
301302
};
302303

@@ -1279,8 +1280,15 @@ export type WorkSeniority = {
12791280
};
12801281

12811282
export type PlaceHolderUsersInput = {
1283+
ciudad?: InputMaybe<Scalars["String"]["input"]>;
12821284
email: Scalars["String"]["input"];
1285+
emergencyPhoneNumber?: InputMaybe<Scalars["String"]["input"]>;
1286+
foodAllergies?: InputMaybe<Scalars["String"]["input"]>;
12831287
name: Scalars["String"]["input"];
1288+
nombreOrganizacion?: InputMaybe<Scalars["String"]["input"]>;
1289+
pais?: InputMaybe<Scalars["String"]["input"]>;
1290+
rolEnOrganizacion?: InputMaybe<Scalars["String"]["input"]>;
1291+
trabajasEnOrganizacion?: InputMaybe<Scalars["String"]["input"]>;
12841292
};
12851293

12861294
export type UpdateUserDataInput = {

src/notifications/tickets.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ORM_TYPE } from "~/datasources/db";
2-
import { USER } from "~/datasources/db/users";
32
import {
43
insertUserTicketsEmailLogSchema,
54
userTicketsEmailLogSchema,
@@ -89,7 +88,6 @@ export const sendAddedToWaitlistEmail = async ({
8988

9089
export const sendTicketInvitationEmails = async ({
9190
DB,
92-
logger,
9391
userTicketIds,
9492
RPC_SERVICE_EMAIL,
9593
}: {

src/schema/events/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
import { getImagesBySanityEventId } from "~/datasources/sanity/images";
2121
import { eventsFetcher } from "~/schema/events/eventsFetcher";
2222
import { schedulesFetcher } from "~/schema/schedules/schedulesFetcher";
23-
import { ScheduleLoadable, ScheduleRef } from "~/schema/schedules/types";
23+
import { ScheduleRef } from "~/schema/schedules/types";
2424
import {
2525
CommunityRef,
2626
EventRef,

src/schema/invitations/mutations.ts

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { eq } from "drizzle-orm";
21
import { GraphQLError } from "graphql";
32

43
import { builder } from "~/builder";
@@ -8,16 +7,20 @@ import {
87
userTicketsSchema,
98
} from "~/datasources/db/schema";
109
import { applicationError, ServiceErrors } from "~/errors";
11-
import { sendTicketInvitationEmails } from "~/notifications/tickets";
10+
import {
11+
sendActualUserTicketQREmails,
12+
sendTicketInvitationEmails,
13+
} from "~/notifications/tickets";
1214
import { createInitialPurchaseOrder } from "~/schema/purchaseOrder/helpers";
1315
import { UserTicketRef } from "~/schema/shared/refs";
1416
import { ticketsFetcher } from "~/schema/ticket/ticketsFetcher";
1517

1618
const GiftTicketsToUserInput = builder.inputType("GiftTicketsToUserInput", {
1719
fields: (t) => ({
18-
ticketId: t.string({ required: true }),
20+
ticketIds: t.stringList({ required: true }),
1921
userIds: t.stringList({ required: true }),
2022
allowMultipleTicketsPerUsers: t.boolean({ required: true }),
23+
autoApproveTickets: t.boolean({ required: true }),
2124
notifyUsers: t.boolean({ required: true }),
2225
}),
2326
});
@@ -43,8 +46,13 @@ builder.mutationField("giftTicketsToUsers", (t) =>
4346
throw new GraphQLError("User not found");
4447
}
4548

46-
const { ticketId, allowMultipleTicketsPerUsers, notifyUsers } = input;
47-
let userIds = input.userIds;
49+
const {
50+
ticketIds,
51+
allowMultipleTicketsPerUsers,
52+
notifyUsers,
53+
autoApproveTickets,
54+
userIds,
55+
} = input;
4856

4957
if (userIds.length === 0) {
5058
throw applicationError(
@@ -54,46 +62,92 @@ builder.mutationField("giftTicketsToUsers", (t) =>
5462
);
5563
}
5664

57-
const tickets = await ticketsFetcher.searchTickets({
65+
const actualTickets = await ticketsFetcher.searchTickets({
5866
DB,
5967
search: {
60-
ticketIds: [ticketId],
68+
ticketIds,
6169
},
6270
});
71+
const actualTicketIds = actualTickets.map((ticket) => ticket.id);
6372

64-
const ticket = tickets[0];
73+
const usersWithTickets = await DB.query.userTicketsSchema.findMany({
74+
where: (u, { and, inArray }) =>
75+
and(
76+
inArray(u.userId, userIds),
77+
inArray(u.ticketTemplateId, actualTicketIds),
78+
),
79+
});
6580

66-
if (!ticket) {
81+
let ticketTemplatesUsersMap = new Map<string, Set<string>>();
82+
83+
for (const ticket of actualTickets) {
84+
ticketTemplatesUsersMap.set(ticket.id, new Set());
85+
}
86+
87+
usersWithTickets.forEach((userWithTicket) => {
88+
if (userWithTicket.userId) {
89+
ticketTemplatesUsersMap
90+
.get(userWithTicket.ticketTemplateId)
91+
?.add(userWithTicket.userId);
92+
}
93+
});
94+
95+
if (ticketTemplatesUsersMap.size === 0) {
6796
throw applicationError(
6897
"Ticket not found",
6998
ServiceErrors.NOT_FOUND,
7099
logger,
71100
);
72101
}
73102

103+
if (!allowMultipleTicketsPerUsers) {
104+
const clearedTicketTemplatesUserMap = new Map<string, Set<string>>();
105+
106+
ticketTemplatesUsersMap.forEach((existingUserSet, ticketTemplateId) => {
107+
const newUserSet = new Set<string>();
108+
109+
userIds.forEach((userId) => {
110+
if (!existingUserSet.has(userId)) {
111+
newUserSet.add(userId);
112+
}
113+
});
114+
115+
clearedTicketTemplatesUserMap.set(ticketTemplateId, newUserSet);
116+
});
117+
118+
ticketTemplatesUsersMap = clearedTicketTemplatesUserMap;
119+
}
120+
121+
if (userIds.length === 0) {
122+
throw applicationError(
123+
"All provided users already have tickets",
124+
ServiceErrors.INVALID_ARGUMENT,
125+
logger,
126+
);
127+
}
128+
74129
const purchaseOrder = await createInitialPurchaseOrder({
75130
DB,
76131
logger,
77132
userId: USER.id,
78133
});
79134

80-
if (!allowMultipleTicketsPerUsers) {
81-
const usersWithTickets = await DB.query.userTicketsSchema.findMany({
82-
where: (u, { and, inArray }) =>
83-
and(inArray(u.userId, userIds), eq(u.ticketTemplateId, ticket.id)),
84-
columns: {
85-
userId: true,
86-
},
87-
});
135+
const ticketsToInsert: (typeof insertUserTicketsSchema._type)[] = [];
88136

89-
const userIdsWithTickets = new Set(
90-
usersWithTickets.map((user) => user.userId),
91-
);
137+
ticketTemplatesUsersMap.forEach((userSet, ticketTemplateId) => {
138+
userSet.forEach((userId) => {
139+
const parsedData = insertUserTicketsSchema.parse({
140+
userId,
141+
ticketTemplateId,
142+
purchaseOrderId: purchaseOrder.id,
143+
approvalStatus: autoApproveTickets ? "approved" : "gifted",
144+
});
92145

93-
userIds = userIds.filter((userId) => !userIdsWithTickets.has(userId));
94-
}
146+
ticketsToInsert.push(parsedData);
147+
});
148+
});
95149

96-
if (userIds.length === 0) {
150+
if (!ticketsToInsert.length) {
97151
throw applicationError(
98152
"All provided users already have tickets",
99153
ServiceErrors.INVALID_ARGUMENT,
@@ -102,29 +156,29 @@ builder.mutationField("giftTicketsToUsers", (t) =>
102156
}
103157

104158
const createdUserTickets = await DB.insert(userTicketsSchema)
105-
.values(
106-
userIds.map((userId) =>
107-
insertUserTicketsSchema.parse({
108-
userId,
109-
ticketTemplateId: ticket.id,
110-
purchaseOrderId: purchaseOrder.id,
111-
approvalStatus: "gifted",
112-
}),
113-
),
114-
)
159+
.values(ticketsToInsert)
115160
.returning();
116161

117162
if (notifyUsers) {
118163
const userTicketIds = createdUserTickets.map(
119164
(userTicket) => userTicket.id,
120165
);
121166

122-
await sendTicketInvitationEmails({
123-
DB,
124-
logger,
125-
userTicketIds,
126-
RPC_SERVICE_EMAIL,
127-
});
167+
if (autoApproveTickets) {
168+
await sendActualUserTicketQREmails({
169+
DB,
170+
logger,
171+
userTicketIds,
172+
RPC_SERVICE_EMAIL,
173+
});
174+
} else {
175+
await sendTicketInvitationEmails({
176+
DB,
177+
logger,
178+
userTicketIds,
179+
RPC_SERVICE_EMAIL,
180+
});
181+
}
128182
}
129183

130184
return createdUserTickets.map((userTicket) =>

src/schema/invitations/tests/giftTicketsToUsers.test.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ describe("Should send tickets to users in bulk", () => {
2929
document: GiftTicketsToUsers,
3030
variables: {
3131
input: {
32-
allowMultipleTicketsPerUsers: true,
33-
ticketId: ticket.id,
32+
allowMultipleTicketsPerUsers: false,
33+
ticketIds: [ticket.id],
3434
userIds: [user1.id, user2.id],
3535
notifyUsers: false,
36+
autoApproveTickets: false,
3637
},
3738
},
3839
});
@@ -57,10 +58,11 @@ describe("Should send tickets to users in bulk", () => {
5758
document: GiftTicketsToUsers,
5859
variables: {
5960
input: {
60-
allowMultipleTicketsPerUsers: true,
61-
ticketId: ticket.id,
61+
allowMultipleTicketsPerUsers: false,
62+
ticketIds: [ticket.id],
6263
userIds: [user1.id],
6364
notifyUsers: false,
65+
autoApproveTickets: false,
6466
},
6567
},
6668
});
@@ -73,9 +75,10 @@ describe("Should send tickets to users in bulk", () => {
7375
variables: {
7476
input: {
7577
allowMultipleTicketsPerUsers: false,
76-
ticketId: ticket.id,
78+
ticketIds: [ticket.id],
7779
userIds: [user1.id, user2.id],
7880
notifyUsers: false,
81+
autoApproveTickets: false,
7982
},
8083
},
8184
});
@@ -100,10 +103,11 @@ describe("Should fail send tickets to users in bulk", () => {
100103
document: GiftTicketsToUsers,
101104
variables: {
102105
input: {
103-
allowMultipleTicketsPerUsers: true,
104-
ticketId: ticket.id,
106+
allowMultipleTicketsPerUsers: false,
107+
ticketIds: [ticket.id],
105108
userIds: [],
106109
notifyUsers: false,
110+
autoApproveTickets: false,
107111
},
108112
},
109113
});
@@ -127,9 +131,10 @@ describe("Should fail send tickets to users in bulk", () => {
127131
variables: {
128132
input: {
129133
allowMultipleTicketsPerUsers: false,
130-
ticketId: ticket.id,
134+
ticketIds: [ticket.id],
131135
userIds: [user1.id, user2.id],
132136
notifyUsers: false,
137+
autoApproveTickets: false,
133138
},
134139
},
135140
});
@@ -141,9 +146,10 @@ describe("Should fail send tickets to users in bulk", () => {
141146
variables: {
142147
input: {
143148
allowMultipleTicketsPerUsers: false,
144-
ticketId: ticket.id,
149+
ticketIds: [ticket.id],
145150
userIds: [user1.id, user2.id],
146151
notifyUsers: false,
152+
autoApproveTickets: false,
147153
},
148154
},
149155
});

0 commit comments

Comments
 (0)