feat(cloud): create subscribers
This commit is contained in:
@@ -53,6 +53,9 @@ export const env = createEnv({
|
|||||||
LINKEDIN_CLIENT_ID: z.string().optional(),
|
LINKEDIN_CLIENT_ID: z.string().optional(),
|
||||||
LINKEDIN_CLIENT_SECRET: z.string().optional(),
|
LINKEDIN_CLIENT_SECRET: z.string().optional(),
|
||||||
NOVU_API_KEY: z.string().optional(),
|
NOVU_API_KEY: z.string().optional(),
|
||||||
|
SUBSCRIBER_API_URL: z.string().url().optional(),
|
||||||
|
SUBSCRIBER_API_KEY: z.string().optional(),
|
||||||
|
SUBSCRIBER_ENVIRONMENT_ID: z.string().optional(),
|
||||||
EMAIL_UNSUBSCRIBE_SECRET: z.string().optional(),
|
EMAIL_UNSUBSCRIBE_SECRET: z.string().optional(),
|
||||||
// Generic OIDC Provider
|
// Generic OIDC Provider
|
||||||
OIDC_CLIENT_ID: z.string().optional(),
|
OIDC_CLIENT_ID: z.string().optional(),
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ services:
|
|||||||
- NOVU_API_KEY=${NOVU_API_KEY}
|
- NOVU_API_KEY=${NOVU_API_KEY}
|
||||||
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL}
|
- DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL}
|
||||||
- EMAIL_UNSUBSCRIBE_SECRET=${EMAIL_UNSUBSCRIBE_SECRET}
|
- EMAIL_UNSUBSCRIBE_SECRET=${EMAIL_UNSUBSCRIBE_SECRET}
|
||||||
|
- SUBSCRIBER_API_URL=${SUBSCRIBER_API_URL}
|
||||||
|
- SUBSCRIBER_API_KEY=${SUBSCRIBER_API_KEY}
|
||||||
|
- SUBSCRIBER_ENVIRONMENT_ID=${SUBSCRIBER_ENVIRONMENT_ID}
|
||||||
|
|
||||||
# S3 storage
|
# S3 storage
|
||||||
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
|
- S3_ACCESS_KEY_ID=${S3_ACCESS_KEY_ID}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { env } from "next-runtime-env";
|
|||||||
import type { dbClient } from "@kan/db/client";
|
import type { dbClient } from "@kan/db/client";
|
||||||
import * as memberRepo from "@kan/db/repository/member.repo";
|
import * as memberRepo from "@kan/db/repository/member.repo";
|
||||||
import * as userRepo from "@kan/db/repository/user.repo";
|
import * as userRepo from "@kan/db/repository/user.repo";
|
||||||
import { notificationClient } from "@kan/email";
|
import { createSubscriber, notificationClient } from "@kan/email";
|
||||||
import { createLogger } from "@kan/logger";
|
import { createLogger } from "@kan/logger";
|
||||||
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
|
import { createEmailUnsubscribeLink, createS3Client } from "@kan/shared";
|
||||||
|
|
||||||
const log = createLogger("auth");
|
|
||||||
|
|
||||||
import { downloadImage } from "./utils";
|
import { downloadImage } from "./utils";
|
||||||
|
|
||||||
|
const log = createLogger("auth");
|
||||||
|
|
||||||
type BetterAuthUser = {
|
type BetterAuthUser = {
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
@@ -95,18 +95,26 @@ export function createDatabaseHooks(db: dbClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (notificationClient) {
|
if (notificationClient) {
|
||||||
|
const [firstName, ...rest] = (user.name || "")
|
||||||
|
.split(" ")
|
||||||
|
.filter(Boolean);
|
||||||
|
const lastName = rest.length ? rest.join(" ") : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [firstName, ...rest] = (user.name || "")
|
|
||||||
.split(" ")
|
|
||||||
.filter(Boolean);
|
|
||||||
const lastName = rest.length ? rest.join(" ") : undefined;
|
|
||||||
const avatarUrl = avatarKey
|
const avatarUrl = avatarKey
|
||||||
? `${env("NEXT_PUBLIC_STORAGE_URL")}/${env("NEXT_PUBLIC_AVATAR_BUCKET_NAME")}/${avatarKey}`
|
? `${env("NEXT_PUBLIC_STORAGE_URL")}/${env("NEXT_PUBLIC_AVATAR_BUCKET_NAME")}/${avatarKey}`
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
|
const unsubscribeUrl = await createEmailUnsubscribeLink(user.id);
|
||||||
|
|
||||||
log.info({ workflowId: "user-signup", userId: user.id, email: user.email }, "Triggering Novu workflow");
|
log.info(
|
||||||
|
{
|
||||||
|
workflowId: "user-signup",
|
||||||
|
userId: user.id,
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
"Triggering Novu workflow",
|
||||||
|
);
|
||||||
await notificationClient.trigger({
|
await notificationClient.trigger({
|
||||||
to: {
|
to: {
|
||||||
subscriberId: user.id,
|
subscriberId: user.id,
|
||||||
@@ -126,7 +134,10 @@ export function createDatabaseHooks(db: dbClient) {
|
|||||||
},
|
},
|
||||||
workflowId: "user-signup",
|
workflowId: "user-signup",
|
||||||
});
|
});
|
||||||
log.info({ workflowId: "user-signup", userId: user.id }, "Novu workflow triggered");
|
log.info(
|
||||||
|
{ workflowId: "user-signup", userId: user.id },
|
||||||
|
"Novu workflow triggered",
|
||||||
|
);
|
||||||
|
|
||||||
await notificationClient.subscribers.credentials.update(
|
await notificationClient.subscribers.credentials.update(
|
||||||
{
|
{
|
||||||
@@ -139,7 +150,22 @@ export function createDatabaseHooks(db: dbClient) {
|
|||||||
user.id,
|
user.id,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error({ err: error }, "Error adding user to notification client");
|
log.error(
|
||||||
|
{ err: error },
|
||||||
|
"Error adding user to notification client",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await createSubscriber({
|
||||||
|
email: user.email,
|
||||||
|
externalId: user.id,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
name: user.name,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
log.error({ err: error }, "Error creating subscriber");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export const name = "email";
|
|||||||
|
|
||||||
export { sendEmail } from "./sendEmail";
|
export { sendEmail } from "./sendEmail";
|
||||||
export { notificationClient } from "./notificationClient";
|
export { notificationClient } from "./notificationClient";
|
||||||
|
export { createSubscriber } from "./subscriberClient";
|
||||||
|
|||||||
53
packages/email/src/subscriberClient.ts
Normal file
53
packages/email/src/subscriberClient.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { createLogger } from "@kan/logger";
|
||||||
|
|
||||||
|
const log = createLogger("subscriberClient");
|
||||||
|
|
||||||
|
export const subscriberClient =
|
||||||
|
process.env.NEXT_PUBLIC_KAN_ENV === "cloud" &&
|
||||||
|
process.env.SUBSCRIBER_API_URL &&
|
||||||
|
process.env.SUBSCRIBER_API_KEY &&
|
||||||
|
process.env.SUBSCRIBER_ENVIRONMENT_ID
|
||||||
|
? {
|
||||||
|
apiUrl: process.env.SUBSCRIBER_API_URL,
|
||||||
|
apiKey: process.env.SUBSCRIBER_API_KEY,
|
||||||
|
environmentId: process.env.SUBSCRIBER_ENVIRONMENT_ID,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
|
||||||
|
interface CreateSubscriberInput {
|
||||||
|
email: string;
|
||||||
|
externalId: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSubscriber(input: CreateSubscriberInput) {
|
||||||
|
if (!subscriberClient) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`${subscriberClient.apiUrl}/environments/${subscriberClient.environmentId}/subscribers`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-API-Key": subscriberClient.apiKey,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(input),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
log.error(
|
||||||
|
{
|
||||||
|
status: response.status,
|
||||||
|
body: await response.text().catch(() => undefined),
|
||||||
|
},
|
||||||
|
"Failed to create subscriber.dev subscriber",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error({ err: error }, "Error creating subscriber.dev subscriber");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user