Thanks to visit codestin.com
Credit goes to github.com

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
7 changes: 4 additions & 3 deletions apps/web/app/(ee)/api/singular/webhook/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { trackSingularLeadEvent } from "@/lib/integrations/singular/track-lead";
import { trackSingularSaleEvent } from "@/lib/integrations/singular/track-sale";
import { prisma } from "@dub/prisma";
import { getSearchParams } from "@dub/utils";
import { AxiomRequest, withAxiom } from "next-axiom";
import { NextResponse } from "next/server";
import { z } from "zod";

Expand Down Expand Up @@ -36,7 +37,7 @@ const authSchema = z.object({
const singularWebhookToken = process.env.SINGULAR_WEBHOOK_TOKEN;

// GET /api/singular/webhook – listen to Postback events from Singular
export const GET = async (req: Request) => {
export const GET = withAxiom(async (req: AxiomRequest) => {
try {
if (!singularWebhookToken) {
throw new DubApiError({
Expand Down Expand Up @@ -114,8 +115,8 @@ export const GET = async (req: Request) => {
} catch (error) {
return handleAndReturnErrorResponse(error);
}
};
});

export const HEAD = async () => {
export const HEAD = () => {
return new Response("OK");
};
188 changes: 145 additions & 43 deletions apps/web/lib/integrations/slack/transform.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { APP_DOMAIN, currencyFormatter } from "@dub/utils";
import { APP_DOMAIN, currencyFormatter, truncate } from "@dub/utils";
import { LinkWebhookEvent } from "dub/models/components";
import { z } from "zod";
import { WebhookTrigger } from "../../types";
import { webhookPayloadSchema } from "../../webhook/schemas";
import {
BountyEventWebhookPayload,
ClickEventWebhookPayload,
CommissionEventWebhookPayload,
LeadEventWebhookPayload,
PartnerEventWebhookPayload,
SaleEventWebhookPayload,
} from "../../webhook/types";

const createLinkTemplate = ({
const linkTemplates = ({
data,
event,
}: {
Expand Down Expand Up @@ -50,7 +51,7 @@ const createLinkTemplate = ({
};
};

const clickLinkTemplate = ({ data }: { data: ClickEventWebhookPayload }) => {
const linkClickedTemplate = ({ data }: { data: ClickEventWebhookPayload }) => {
const { link, click } = data;
const linkToClicks = `${APP_DOMAIN}/events?event=clicks&domain=${link.domain}&key=${link.key}`;

Expand Down Expand Up @@ -102,7 +103,7 @@ const clickLinkTemplate = ({ data }: { data: ClickEventWebhookPayload }) => {
};
};

const createLeadTemplate = ({ data }: { data: LeadEventWebhookPayload }) => {
const leadCreatedTemplate = ({ data }: { data: LeadEventWebhookPayload }) => {
const { customer, click, link } = data;
const linkToLeads = `${APP_DOMAIN}/events?event=leads&domain=${link.domain}&key=${link.key}`;

Expand Down Expand Up @@ -151,7 +152,7 @@ const createLeadTemplate = ({ data }: { data: LeadEventWebhookPayload }) => {
};
};

const createSaleTemplate = ({ data }: { data: SaleEventWebhookPayload }) => {
const saleCreatedTemplate = ({ data }: { data: SaleEventWebhookPayload }) => {
const { customer, click, sale, link } = data;
const amountInDollars = (sale.amount / 100).toFixed(2);
const linkToSales = `${APP_DOMAIN}/events?event=sales&domain=${link.domain}&key=${link.key}`;
Expand Down Expand Up @@ -201,12 +202,13 @@ const createSaleTemplate = ({ data }: { data: SaleEventWebhookPayload }) => {
};
};

const enrolledPartnerTemplate = ({
const partnerEnrolledTemplate = ({
data,
}: {
data: PartnerEventWebhookPayload;
}) => {
const { name, email, country } = data;
const { name, email, country, partnerId } = data;
const linkToPartner = `${APP_DOMAIN}/program/partners?partnerId=${partnerId}`;

return {
blocks: [
Expand Down Expand Up @@ -234,22 +236,29 @@ const enrolledPartnerTemplate = ({
},
],
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `Partner ID: ${partnerId} | <${linkToPartner}|View on Dub>`,
},
],
},
],
};
};

// TODO (kiran):
// We should improve this template
const commissionCreatedTemplate = ({
data,
}: {
data: CommissionEventWebhookPayload;
}) => {
const { id, amount, earnings } = data;
const { id, amount, earnings, currency, partner, customer } = data;

const formattedAmount = currencyFormatter(amount / 100);

const formattedEarnings = currencyFormatter(earnings / 100);
const formattedAmount = currencyFormatter(amount / 100, { currency });
const formattedEarnings = currencyFormatter(earnings / 100, { currency });
const linkToCommissions = `${APP_DOMAIN}/program/commissions`;

return {
blocks: [
Expand All @@ -265,52 +274,144 @@ const commissionCreatedTemplate = ({
fields: [
{
type: "mrkdwn",
text: `*Commission ID*\n${id}`,
text: `*Partner*\n${partner.name}`,
},
{
type: "mrkdwn",
text: `*Email*\n<mailto:${partner.email}|${partner.email}>`,
},
],
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Commission Amount*\n${formattedAmount}`,
},
{
type: "mrkdwn",
text: `*Amount*\n${formattedAmount}`,
text: `*Partner Earnings*\n${formattedEarnings}`,
},
],
},
...(customer
? [
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Customer*\n${customer.name}`,
},
{
type: "mrkdwn",
text: `*Customer Email*\n<mailto:${customer.email}|${customer.email}>`,
},
],
},
]
: []),
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `*Earnings*\n${formattedEarnings}`,
text: `Commission ID: ${id} | <${linkToCommissions}|View on Dub>`,
},
],
},
],
};
};

// Minimal bounty templates (safe defaults)
const bountyCreatedTemplate = ({} /* data */ : { data: unknown }) => ({
blocks: [
{
type: "section",
text: { type: "mrkdwn", text: "*New bounty created* :money_with_wings:" },
},
],
});
const bountyTemplates = ({
data,
event,
}: {
data: BountyEventWebhookPayload;
event: WebhookTrigger;
}) => {
const { id, name, description, rewardAmount, type, startsAt, endsAt } = data;

const eventMessages = {
"bounty.created": "*New bounty created* :money_with_wings:",
"bounty.updated": "*Bounty updated* :memo:",
};

const formattedReward = currencyFormatter(rewardAmount / 100);
const linkToBounty = `${APP_DOMAIN}/program/bounties/${id}`;

const bountyUpdatedTemplate = ({} /* data */ : { data: unknown }) => ({
blocks: [
{
type: "section",
text: { type: "mrkdwn", text: "*Bounty updated* :memo:" },
},
],
});
return {
blocks: [
{
type: "section",
text: {
type: "mrkdwn",
text: eventMessages[event],
},
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Bounty Name*\n${truncate(name, 140) || "Untitled Bounty"}`,
},
{
type: "mrkdwn",
text: `*Reward Amount*\n${formattedReward}`,
},
],
},
{
type: "section",
fields: [
{
type: "mrkdwn",
text: `*Type*\n${type.charAt(0).toUpperCase() + type.slice(1)}`,
},
{
type: "mrkdwn",
text: `*Duration*\n${new Date(startsAt).toLocaleDateString()}${endsAt ? ` - ${new Date(endsAt).toLocaleDateString()}` : " (No end date)"}`,
},
],
},
...(description
? [
{
type: "section",
text: {
type: "mrkdwn",
text: `*Description*\n${truncate(description, 140)}`,
},
},
]
: []),
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `<${linkToBounty}|View on Dub>`,
},
],
},
],
};
};

const slackTemplates: Record<WebhookTrigger, any> = {
"link.created": createLinkTemplate,
"link.updated": createLinkTemplate,
"link.deleted": createLinkTemplate,
"link.clicked": clickLinkTemplate,
"lead.created": createLeadTemplate,
"sale.created": createSaleTemplate,
"partner.enrolled": enrolledPartnerTemplate,
"link.created": linkTemplates,
"link.updated": linkTemplates,
"link.deleted": linkTemplates,
"link.clicked": linkClickedTemplate,
"lead.created": leadCreatedTemplate,
"sale.created": saleCreatedTemplate,
"partner.enrolled": partnerEnrolledTemplate,
"commission.created": commissionCreatedTemplate,
"bounty.created": bountyCreatedTemplate,
"bounty.updated": bountyUpdatedTemplate,
"bounty.created": bountyTemplates,
"bounty.updated": bountyTemplates,
};

export const formatEventForSlack = (
Expand All @@ -326,9 +427,10 @@ export const formatEventForSlack = (
const isLinkEvent = ["link.created", "link.updated", "link.deleted"].includes(
event,
);
const isBountyEvent = ["bounty.created", "bounty.updated"].includes(event);

return template({
data,
...(isLinkEvent && { event }),
...((isLinkEvent || isBountyEvent) && { event }),
});
};
4 changes: 4 additions & 0 deletions apps/web/lib/integrations/slack/ui/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const SlackSettings = (props: InstalledIntegrationInfoProps) => {
"link.clicked",
"lead.created",
"sale.created",
"partner.enrolled",
"commission.created",
"bounty.created",
"bounty.updated",
]}
/>
)}
Expand Down