Skip to content
Open
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
4 changes: 3 additions & 1 deletion libs/inngest/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { schema as googleSchema } from './integrations/google/index.js';
import { schema as sanitySchema } from './integrations/sanity/index.js';
import { schema as stripeSchema } from './integrations/stripe/index.js';
import { schema as websiteSchema } from './integrations/website/index.js';
import { schema as kitSchema } from './integrations/kit/index.js';

// TODO pin Zod to 3 or figure out other workaround for
// https://github.com/inngest/inngest-js/issues/1014
Expand All @@ -16,7 +17,8 @@ export const schemas = new EventSchemas()
.fromZod(googleSchema)
.fromZod(sanitySchema)
.fromZod(stripeSchema)
.fromZod(websiteSchema);
.fromZod(websiteSchema)
.fromZod(kitSchema);

if (!process.env.INNGEST_EVENT_KEY) {
console.error('missing INNGEST_EVENT_KEY. Workflows will not run.');
Expand Down
2 changes: 2 additions & 0 deletions libs/inngest/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
handleUpdateUserProfile,
handleWDCIntakeSubmit,
} from './integrations/website/steps.js';
import { tagSubscriber } from './integrations/kit/steps.js';

export { inngest } from './client.js';

Expand Down Expand Up @@ -94,4 +95,5 @@ export const functions: any[] = [
handleLWJIntake,
handleUpdateUserProfile,
handleWDCIntakeSubmit,
tagSubscriber,
];
1 change: 1 addition & 0 deletions libs/inngest/src/integrations/kit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { schema } from './types.js';
18 changes: 18 additions & 0 deletions libs/inngest/src/integrations/kit/steps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { inngest } from '../../client.js';
import { tagSubscriber as tagSubscriberKit } from '@codetv/kit';

export const tagSubscriber = inngest.createFunction(
{ id: 'kit/subscriber.tag.add' },
{ event: 'kit/subscriber.tag.add' },
async function ({
event,
step,
}: {
event: any;
step: any;
}): Promise<{ subscription: any }> {
return await step.run('tag-subscriber', async () => {
return await tagSubscriberKit(event.data.email, event.data.tagName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be staring right at it, but what happens if this email is not subscribed? we probably need to check for that kind of failure and signal that it's non-retriable so Inngest doesn't keep retrying

});
},
);
10 changes: 10 additions & 0 deletions libs/inngest/src/integrations/kit/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { z } from 'zod';

export const schema = {
'kit/subscriber.tag.add': {
data: z.object({
email: z.string(),
tag: z.string(),
}),
},
};
2 changes: 1 addition & 1 deletion libs/inngest/src/integrations/sanity/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { inngest } from '../../client.js';
export const getCurrentActiveHackathon = inngest.createFunction(
{ id: 'sanity/hackathon.get-current-active' },
{ event: 'sanity/hackathon.get-current-active' },
async function ({ event }): Promise<{ _id: string } | null> {
async function ({ event }): Promise<{ _id: string; slug: string } | null> {
return await getActiveHackathon();
},
);
Expand Down
9 changes: 9 additions & 0 deletions libs/inngest/src/integrations/website/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
updateUserRole,
} from '../discord/steps.ts';
import { userGetById } from '../clerk/steps.ts';
import { tagSubscriber } from '../kit/steps.ts';

export const handleUpdateUserProfile = inngest.createFunction(
{ id: 'codetv/user.profile.update' },
Expand Down Expand Up @@ -246,6 +247,14 @@ export const handleHackathonSubmission = inngest.createFunction(
optOutSponsorship: event.data.optOutSponsorship,
},
}),
// Tag subscriber in Kit
step.invoke('tag-subscriber', {
function: tagSubscriber,
data: {
email: event.data.email,
tagName: `wdc-hackathon-${hackathon?.slug}`,
},
}),
]);

return submission;
Expand Down
75 changes: 66 additions & 9 deletions libs/kit/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
const ck_api = new URL('https://api.convertkit.com');
const api_key = process.env.KIT_SECRET_KEY;

if (api_key) {
ck_api.searchParams.set('api_secret', api_key);
} else {
if (!api_key) {
console.error(
'KIT_SECRET_KEY is not set in env. Newsletter activities will not work.',
);
}

function createApiUrl(pathname: string) {
const url = new URL(pathname, CK_BASE_URL);
url.searchParams.set('api_secret', api_key!);
return url;
}

export async function addSubscriber(first_name: string, email: string) {
/** @see https://app.convertkit.com/forms/designers/1269192/edit */
ck_api.pathname = '/v3/forms/1269192/subscribe';
const url = createApiUrl('/v3/forms/1269192/subscribe');

const response = await fetch(ck_api, {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
api_key,
api_secret: api_key,
first_name,
email,
}),
Expand All @@ -44,10 +48,10 @@ export async function addSubscriber(first_name: string, email: string) {
}

export async function getSubscriberByEmail(email: string) {
ck_api.pathname = '/v3/subscribers';
ck_api.searchParams.set('email_address', email);
const url = createApiUrl('/v3/subscribers');
url.searchParams.set('email_address', email);

const res = await fetch(ck_api);
const res = await fetch(url);

if (!res.ok) {
console.error(res.statusText);
Expand All @@ -59,3 +63,56 @@ export async function getSubscriberByEmail(email: string) {

return data.subscribers.at(0);
}

export async function getTags() {
const url = createApiUrl('/v3/tags');

const res = await fetch(url);

if (!res.ok) {
console.error(res.statusText);
throw new Error('Error fetching tags');
}

const data = (await res.json()) as any;

return data.tags as Array<{ id: number; name: string }>;
}

export async function tagSubscriber(email: string, tagName: string) {
if (!tagName) {
throw new Error('tagName is required to tag a subscriber');
}

// Look up the tag ID by name
const tags = await getTags();
const tag = tags.find((t) => t.name?.toLowerCase() === tagName.toLowerCase());

if (!tag) {
throw new Error(`Tag "${tagName}" not found in Kit`);
}

const url = createApiUrl(`/v3/tags/${tag.id}/subscribe`);

const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({
api_secret: api_key,
email,
}),
});

if (!res.ok) {
const errorText = await res.text();
console.error(`Error tagging subscriber: ${res.status} ${res.statusText}`);
console.error(errorText);
throw new Error(`Error tagging subscriber: ${res.statusText}`);
}

const data = (await res.json()) as any;

return data.subscription;
}
1 change: 1 addition & 0 deletions libs/kit/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"allowImportingTsExtensions": true,
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist",
Expand Down
12 changes: 6 additions & 6 deletions libs/sanity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,8 @@ const hackathonBySlugQuery = groq`

const activeHackathonQuery = groq`
*[_type == "hackathon" && hidden != "hidden" && dateTime(pubDate) <= dateTime(now()) && dateTime(deadline) > dateTime(now())] | order(pubDate desc)[0] {
_id
_id,
'slug': slug.current,
}
`;

Expand Down Expand Up @@ -813,11 +814,10 @@ export async function getHackathonBySlug(params: { slug: string }) {
}

export async function getActiveHackathon() {
const hackathon = await client.fetch<{ _id: string } | null>(
activeHackathonQuery,
{},
{ useCdn: true },
);
const hackathon = await client.fetch<{
_id: string;
slug: string;
} | null>(activeHackathonQuery, {}, { useCdn: true });

if (!hackathon) {
return null;
Expand Down