Skip to content

Commit 6bf98e4

Browse files
Krish120003arian81
andauthored
feat: enable apple wallet pass creation (#411)
* feat: deltahacks 12 apple wallet passes * fix: do an authentication check before generating wallet pass * fix: update to correct identifier * style: format * chore: more linting? * chore: prettier format --------- Co-authored-by: Arian Ahmadinejad <ahmadinejadarian@gmail.com>
1 parent 7c5c001 commit 6bf98e4

14 files changed

Lines changed: 204 additions & 117 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"react-hook-form-persist": "^3.0.0",
7777
"react-icons": "^5.5.0",
7878
"react-markdown": "^10.1.0",
79+
"react-qr-code": "^2.0.18",
7980
"react-select": "^5.10.1",
8081
"react-timelines": "^2.6.1",
8182
"superjson": "^2.2.2",

pnpm-lock.yaml

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/api/wallet/apple/[id]/route.ts

Lines changed: 107 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -2,121 +2,118 @@ import { PKPass } from "passkit-generator";
22
import { promises as fs } from "fs";
33
import { env } from "../../../../../env/server.mjs";
44
import path from "path";
5+
import { authOptions } from "../../../../../pages/api/auth/[...nextauth]";
56

67
import { prisma } from "../../../../../server/db/client";
7-
8-
// // import { db } from "@/server/db";
9-
// import path from "path";
10-
// import { env } from "../../../../../env/server.mjs";
11-
// // async function readFileToBuffer(filePath: string): Promise<Buffer> {
12-
// // try {
13-
// // const buffer = await fs.readFile(filePath);
14-
// // console.log("File buffer:", buffer);
15-
// // return buffer;
16-
// // } catch (err) {
17-
// // console.error("Error reading file:", err);
18-
// // throw err;
19-
// // }
20-
// // }
8+
import { getServerSession } from "next-auth";
219

2210
export const GET = async (
2311
request: Request,
2412
{ params }: { params: Promise<{ id: string }> },
2513
) => {
26-
// TODO: Properly implement Apple Wallet support across years.
27-
return;
28-
29-
// const id = (await params).id;
30-
31-
// const user = await prisma?.user.findFirst({
32-
// where: {
33-
// id: id,
34-
// },
35-
// include: {
36-
// DH11Application: true,
37-
// },
38-
// });
39-
40-
// if (!user) {
41-
// return new Response("User not found", { status: 404 });
42-
// }
43-
44-
// const cardColor = "rgb(94, 51, 184)";
45-
46-
// try {
47-
// /** Each, but last, can be either a string or a Buffer. See API Documentation for more */
48-
49-
// const wwdr = (await prisma.config.findFirst({
50-
// where: {
51-
// name: "APPLE_WWDR",
52-
// },
53-
// }))!.value;
54-
55-
// const signerCert = (await prisma.config.findFirst({
56-
// where: {
57-
// name: "APPLE_SIGNER_CERT",
58-
// },
59-
// }))!.value;
60-
61-
// const signerKey = (await prisma.config.findFirst({
62-
// where: {
63-
// name: "APPLE_SIGNER_KEY",
64-
// },
65-
// }))!.value;
66-
67-
// const signerKeyPassphrase = (await prisma.config.findFirst({
68-
// where: {
69-
// name: "APPLE_SIGNER_KEY_PASSPHRASE",
70-
// },
71-
// }))!.value;
72-
// const pass = await PKPass.from(
73-
// {
74-
// /**
75-
// * Note: .pass extension is enforced when reading a
76-
// * model from FS, even if not specified here below
77-
// */
78-
// model: path.resolve("src/assets/deltahacks_11.pass"),
79-
// certificates: {
80-
// wwdr,
81-
// signerCert,
82-
// signerKey,
83-
// signerKeyPassphrase,
84-
// },
85-
// },
86-
// {
87-
// backgroundColor: cardColor,
88-
// }
89-
// );
90-
91-
// // Adding some settings to be written inside pass.json
92-
// // pass.localize("en", { ... });
93-
// pass.setBarcodes(`${env.NEXT_PUBLIC_URL}/profile/${id}`); // Random value
94-
// // pass.primaryFields.push({ key: "header", value: "" });
95-
96-
// pass.backFields.push({
97-
// key: "ticket-buyer-name",
98-
// label: "For",
99-
// value: `${user.DH11Application?.firstName} ${user.DH11Application?.lastName}`,
100-
// });
101-
102-
// pass.primaryFields.push({
103-
// key: "ticket-for",
104-
// label: "Ticket for",
105-
// value: `${user.DH11Application?.firstName} ${user.DH11Application?.lastName} `,
106-
// });
107-
108-
// // add a background color
109-
110-
// // Generate the stream .pkpass file stream
111-
// const dataBuffer = pass.getAsBuffer();
112-
// return new Response(dataBuffer, {
113-
// headers: {
114-
// "Content-Type": "application/vnd.apple.pkpass",
115-
// "Content-Disposition": "attachment; filename=pass.pkpass",
116-
// },
117-
// });
118-
// } catch (err) {
119-
// console.error(err);
120-
// return new Response("Something went wrong", { status: 500 });
121-
// }
14+
const id = (await params).id;
15+
const session = await getServerSession(authOptions);
16+
17+
if (!session?.user?.id || session.user.id !== id) {
18+
return new Response("Unauthorized", { status: 401 });
19+
}
20+
21+
const user = await prisma?.user.findFirst({
22+
where: {
23+
id: id,
24+
},
25+
include: {
26+
DH12Application: true,
27+
},
28+
});
29+
30+
if (!user) {
31+
return new Response("User not found", { status: 404 });
32+
}
33+
34+
const cardColor = "rgb(94, 51, 184)";
35+
36+
try {
37+
/** Each, but last, can be either a string or a Buffer. See API Documentation for more */
38+
39+
const wwdr = (await prisma.config.findFirst({
40+
where: {
41+
name: "APPLE_WWDR",
42+
},
43+
}))!.value;
44+
45+
const signerCert = (await prisma.config.findFirst({
46+
where: {
47+
name: "APPLE_SIGNER_CERT",
48+
},
49+
}))!.value;
50+
51+
const signerKey = (await prisma.config.findFirst({
52+
where: {
53+
name: "APPLE_SIGNER_KEY",
54+
},
55+
}))!.value;
56+
57+
const signerKeyPassphrase = (await prisma.config.findFirst({
58+
where: {
59+
name: "APPLE_SIGNER_KEY_PASSPHRASE",
60+
},
61+
}))!.value;
62+
const pass = await PKPass.from(
63+
{
64+
/**
65+
* Note: .pass extension is enforced when reading a
66+
* model from FS, even if not specified here below
67+
*/
68+
model: path.resolve("src/assets/deltahacks_12.pass"),
69+
certificates: {
70+
wwdr,
71+
signerCert,
72+
signerKey,
73+
signerKeyPassphrase,
74+
},
75+
},
76+
{
77+
backgroundColor: cardColor,
78+
},
79+
);
80+
81+
// Adding some settings to be written inside pass.json
82+
// pass.localize("en", { ... });
83+
pass.setBarcodes(`${env.NEXT_PUBLIC_URL}/profile/${id}`); // Random value
84+
// pass.primaryFields.push({ key: "header", value: "" });
85+
86+
const firstName =
87+
user.DH12Application?.firstName || user.name?.split(" ")[0] || "Attendee";
88+
const lastName =
89+
user.DH12Application?.lastName ||
90+
user.name?.split(" ").slice(1).join(" ") ||
91+
"";
92+
93+
pass.backFields.push({
94+
key: "ticket-buyer-name",
95+
label: "For",
96+
value: `${firstName} ${lastName}`.trim(),
97+
});
98+
99+
pass.primaryFields.push({
100+
key: "ticket-for",
101+
label: "Ticket for",
102+
value: `${firstName} ${lastName}`.trim(),
103+
});
104+
105+
// add a background color
106+
107+
// Generate the stream .pkpass file stream
108+
const dataBuffer = pass.getAsBuffer();
109+
return new Response(dataBuffer, {
110+
headers: {
111+
"Content-Type": "application/vnd.apple.pkpass",
112+
"Content-Disposition": "attachment; filename=pass.pkpass",
113+
},
114+
});
115+
} catch (err) {
116+
console.error(err);
117+
return new Response("Something went wrong", { status: 500 });
118+
}
122119
};
6.29 KB
Loading
14 KB
Loading
22.6 KB
Loading
10.8 KB
Loading
26.3 KB
Loading
47 KB
Loading
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"passTypeIdentifier": "pass.com.deltahacks",
3+
"serialNumber": "8b3732cc-6bfb-371c-95ed-5cef0cbf774e",
4+
"webServiceURL": "https://portal.deltahacks.com/api/wallet/apple/service/",
5+
"authenticationToken": "abcdefghijklmnopqrstuvwxyz",
6+
"formatVersion": 1,
7+
8+
"description": "Your ticket to DeltaHacks",
9+
"labelColor": "rgb(255, 255, 255)",
10+
"foregroundColor": "rgb(255, 255, 255)",
11+
12+
"locations": [
13+
{ "latitude": 43.26541517386679, "longitude": -79.91827069511017 }
14+
],
15+
"barcodes": [],
16+
"teamIdentifier": "CMGV8D3FG3",
17+
"organizationName": "DeltaHacks",
18+
"eventTicket": {
19+
"headerFields": [
20+
{
21+
"dateStyle": "PKDateStyleMedium",
22+
"value": "2026-01-10T8:00:00-05:00",
23+
"key": "start-time",
24+
"label": "Saturday"
25+
}
26+
],
27+
"auxiliaryFields": [
28+
{
29+
"value": "Peter George Centre for Living and Learning, 1280 Main St W, Hamilton, ON L8S 4L8",
30+
"key": "venue-name",
31+
"label": "WHERE"
32+
}
33+
],
34+
35+
"secondaryFields": [
36+
{
37+
"value": "Saturday, 8 AM - Sunday, 6 PM",
38+
"key": "event-time",
39+
"label": "WHEN"
40+
}
41+
],
42+
"backFields": [
43+
{
44+
"key": "event-name-back",
45+
"label": "Event",
46+
"value": "Deltahacks 12"
47+
},
48+
{
49+
"key": "ticket-type-back",
50+
"label": "Ticket",
51+
"value": "Attendee"
52+
},
53+
{
54+
"key": "venue-address",
55+
"label": "Venue",
56+
"value": "Peter George Centre for Living and Learning, 1280 Main St W, Hamilton, ON L8S 4L8"
57+
},
58+
{
59+
"key": "organizer-name",
60+
"label": "Organized by",
61+
"value": "DeltaHacks"
62+
}
63+
]
64+
}
65+
}

0 commit comments

Comments
 (0)