Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/deploy_dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
# Deploy the email RPC Service
- name: 🛳️ DEPLOY EMAIL-RPC SERVICE WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --minify --config ./workers/transactional_email_service/wrangler.toml
# Deploy the svg-renderer
- name: 🛳️ DEPLOY SVG-RENDERER WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --minify --config ./workers/svg_renderer/wrangler.toml
# Deploy the auth tokens worker
- name: 🛳️ DEPLOY AUTH-TOKEN WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --minify --config ./workers/auth_tokens/wrangler.toml
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy_prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
# Deploy the email RPC Service
- name: 🛳️ DEPLOY EMAIL-RPC SERVICE WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=production --minify --config ./workers/transactional_email_service/wrangler.toml
# Deploy the svg-renderer
- name: 🛳️ DEPLOY SVG-RENDERER WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=production --minify --config ./workers/svg_renderer/wrangler.toml
# Deploy the auth tokens worker
- name: 🛳️ DEPLOY AUTH-TOKEN WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=production --minify --config ./workers/auth_tokens/wrangler.toml
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/deploy_staging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
# Deploy the email RPC Service
- name: 🛳️ DEPLOY EMAIL-RPC SERVICE WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=staging --minify --config ./workers/transactional_email_service/wrangler.toml
# Deploy the svg-renderer
- name: 🛳️ DEPLOY SVG-RENDERER WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=staging --minify --config ./workers/svg_renderer/wrangler.toml
# Deploy the auth tokens worker
- name: 🛳️ DEPLOY AUTH-TOKEN WORKER
run: CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_WORKERS_JSCONF_DEV_DEPLOY }} npx wrangler deploy --env=staging --minify --config ./workers/auth_tokens/wrangler.toml
Expand Down
3 changes: 2 additions & 1 deletion emails/templates/helpers/fonts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,9 @@ const poppins = [

export const PoppinsFont = () => (
<>
{poppins.map((font) => (
{poppins.map((font, index) => (
<Font
key={index}
fontFamily="Poppins"
fallbackFontFamily="Verdana"
webFont={{
Expand Down
5 changes: 0 additions & 5 deletions emails/templates/helpers/tickets.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import {
Body,
Column,
Font,
Head,
Html,
Link,
Preview,
Row,
Tailwind,
} from "@react-email/components";
Expand All @@ -27,8 +24,6 @@ export const TicketTemplate = ({
theme,
font = "poppins",
}: WorkEmailValidationEmailProps & ThemeType) => {
console.log({ font });

return (
<Tailwind
config={{
Expand Down
2 changes: 1 addition & 1 deletion emails/templates/tickets/event-invitation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const EventInvitation = ({

<Text className="mb-16 text-center">
<Button
href="https://communityos.io/tickets"
href="https://communityos.io/ai-hackathon/tickets"
className="bg-black py-4 px-6 rounded-3xl text-gray-200 self-center"
target="_blank"
>
Expand Down
95 changes: 95 additions & 0 deletions emails/templates/tickets/ticket-confirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
Column,
Container,
Hr,
Img,
Link,
Preview,
Row,
Section,
Text,
} from "@react-email/components";
import * as React from "react";

import { BigFooter, TicketTemplate } from "emails/templates/helpers/tickets";

interface EmailProps {
eventName: string;
userTicketId: string;
userName?: string;
eventLogoCloudflareImageURL: string;
userEmail: string;
}

export const TicketConfirmation = ({
eventName,
userTicketId,
userName,
userEmail,
eventLogoCloudflareImageURL,
}: EmailProps) => {
return (
<TicketTemplate theme="light">
<Container className="px-10 py-10 w-full max-w-3xl font-light">
<Section className="">
<Preview>Tu ticket para {eventName}</Preview>
<Row className="h-20 mb-14">
<Column>
<Img
src={`${eventLogoCloudflareImageURL}/w=300,fit=scale-down`}
className="w-full max-w-[200px]"
/>
</Column>
</Row>

<Text className="text-2xl mb-6">
{userName ? `Hola ${userName},` : "Hola,"}
</Text>

<Text className="text-xl mb-8">
Este es tu ticket para el evento:
</Text>

<Text className="text-xl text-center mb-16 px-8 text-gray-400">
{eventName}
</Text>

<Row>
<Img
className="px-20 mb-16"
src={`https://svg-renderer.communityos.io/qr/svg/${userTicketId}`}
/>
</Row>

<Text className="text-xl mb-16">
Recuerda llevar este ticket contigo el día del evento. <br />
También está disponible el tu perfil de CommunityOS, ingresa con tu
correo <span className="font-semibold">{userEmail}</span> en{" "}
<Link href="https://communityos.io/ai-hackathon/tickets">
communityos.io/ai-hackathon/tickets
</Link>
.
</Text>

<Text className="text-xl mb-8">Nos vemos en el evento,</Text>
<Text className="text-xl font-semibold mb-8">Equipo CommunityOS</Text>

<Text className="text-xl mb-8"></Text>
</Section>
<Hr className="my-8 border-black" />
<BigFooter theme="light" />
</Container>
</TicketTemplate>
);
};

TicketConfirmation.PreviewProps = {
eventName: "El Potencial Clave de la Recuperación Aumentada (RAG) con OpenAI",
userName: "John Doe",
userTicketId: "c7fa5dc0-ffa2-4369-bac7-3aa52d7cc640",
eventLogoCloudflareImageURL:
"https://imagedelivery.net/dqFoxiedZNoncKJ9uqxz0g/6cdd148e-b931-4b7a-f983-d75d388aff00",
userEmail: "fake@email.com",
} satisfies EmailProps;

export default TicketConfirmation;
2 changes: 1 addition & 1 deletion emails/templates/tickets/waitlist-accepted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const WaitlistAccepted = ({

<Text className="mb-8 text-center">
<Button
href="https://communityos.io/tickets"
href="https://communityos.io/ai-hackathon/tickets"
className="bg-black py-4 px-6 rounded-3xl text-gray-200 self-center"
target="_blank"
>
Expand Down
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"prettier:fix": "prettier ./src --cache --write",
"typecheck": "tsc",
"dev": "wrangler dev --log-level info",
"dev:svg-renderer": "wrangler dev --test-scheduled --config ./workers/svg_renderer/wrangler.toml",
"dev:wall-of-fame": "wrangler dev --test-scheduled --config ./workers/wall_of_fame_cron/wrangler.toml",
"dev:sanity": "wrangler dev --test-scheduled --config ./workers/sanity_asset_importer/wrangler.toml",
"dev:auth": "wrangler dev --test-scheduled --config ./workers/auth_tokens/wrangler.toml",
Expand Down Expand Up @@ -102,6 +103,7 @@
"mercadopago": "^2.0.9",
"p-map": "^6.0.0",
"postgres": "^3.4.4",
"qr-svg": "^1.1.0",
"react": "^18.2.0",
"resend": "^3.3.0",
"slugify": "^1.6.6",
Expand Down
85 changes: 85 additions & 0 deletions src/notifications/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,91 @@ export const sendTicketInvitationEmails = async ({
to: users.map((user) => ({
name: user.name ?? undefined,
email: user.email,
tags: [],
})),
});
};

export const sendActualUserTicketQREmails = async ({
DB,
logger,
userTicketIds,
RPC_SERVICE_EMAIL,
}: {
DB: ORM_TYPE;
logger: Logger;
userTicketIds: string[];
RPC_SERVICE_EMAIL: Context["RPC_SERVICE_EMAIL"];
}) => {
const userTickets = await DB.query.userTicketsSchema.findMany({
where: (t, { inArray }) => inArray(t.id, userTicketIds),
with: {
user: true,
ticketTemplate: {
with: {
event: true,
},
},
},
});

if (userTickets.length === 0) {
throw applicationError(
`Could not find ticket and event information associated to: ${userTicketIds.join(
", ",
)}`,
ServiceErrors.NOT_FOUND,
logger,
);
}

const to: {
name?: string;
email: string;
tags: { name: string; value: string }[];
userTicketId: string;
}[] = [];

userTickets.forEach((userTicket) => {
if (!userTicket.user) {
return null;
}

to.push({
name: userTicket.user.name ?? undefined,
email: userTicket.user.email,
userTicketId: userTicket.id,
tags: [
{
name: "userTicket",
value: userTicket.id,
},
{
name: "ticketName",
value: userTicket.ticketTemplate.name,
},
{
name: "ticketId",
value: userTicket.ticketTemplate.id,
},
{
name: "eventId",
value: userTicket.ticketTemplate.event.id,
},
{
name: "eventName",
value: userTicket.ticketTemplate.event.name,
},
{
name: "userId",
value: userTicket.user.id,
},
],
});
});

await RPC_SERVICE_EMAIL.bulkSendUserQRTicketEmail({
to,
eventName: userTickets[0].ticketTemplate.event.name,
});
};
15 changes: 14 additions & 1 deletion src/schema/user/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ import {
usersToCommunitiesSchema,
} from "~/datasources/db/schema";
import { applicationError, ServiceErrors } from "~/errors";
import {
sendActualUserTicketQREmails,
sendTicketInvitationEmails,
} from "~/notifications/tickets";
import { UserRef } from "~/schema/shared/refs";
import { pronounsEnum } from "~/schema/user/types";
import { usersFetcher } from "~/schema/user/userFetcher";
Expand Down Expand Up @@ -239,12 +243,21 @@ builder.mutationField("updateMyUserData", (t) =>
}

if (input.eventId) {
await validateUserDataAndApproveUserTickets({
const changedUserTickets = await validateUserDataAndApproveUserTickets({
DB: ctx.DB,
userId: USER.id,
eventId: input.eventId,
logger: ctx.logger,
});

if (changedUserTickets.length) {
await sendActualUserTicketQREmails({
DB: ctx.DB,
logger: ctx.logger,
userTicketIds: changedUserTickets.map((t) => t.id),
RPC_SERVICE_EMAIL: ctx.RPC_SERVICE_EMAIL,
});
}
}

const user = await usersFetcher.searchUsers({
Expand Down
1 change: 1 addition & 0 deletions src/tests/fixtures/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export const MOCKED_RPC_SERVICE_EMAIL = {
sendConfirmationWaitlistAccepted: vitest.fn(),
sendConfirmationWaitlistRejected: vitest.fn(),
bulkSendEventTicketInvitations: vitest.fn(),
bulkSendUserQRTicketEmail: vitest.fn(),
} satisfies MockedService<Context["RPC_SERVICE_EMAIL"]>;
45 changes: 45 additions & 0 deletions workers/svg_renderer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Hono } from "hono";
import { QR } from "qr-svg";

import { createLogger } from "~/logging";
import { isValidUUID } from "~/schema/shared/helpers";

const app = new Hono();

app.get("/qr/raw/:id", (c) => {
const logger = createLogger("qr-render");
const uuid = c.req.param("id").trim().toLowerCase();

if (!isValidUUID(uuid)) {
logger.error("Invalid id");
throw new Error("Invalid id");
}

const svg = QR(uuid);

return c.text(svg);
});

app.get("/qr/svg/:id", (c) => {
const logger = createLogger("qr-render");
const uuid = c.req.param("id").trim().toLowerCase();

if (!isValidUUID(uuid)) {
logger.error("Invalid id");
throw new Error("Invalid id");
}

const svg = QR(uuid);

c.res.headers.set("Content-Type", "image/svg+xml");

return c.text(svg);
});

app.get("/", (c) => {
return c.json({
message: "Greetings and salutations from the CommunityOS team",
});
});

export default app;
Loading