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/api/links/info/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { transformLink } from "@/lib/api/links";
import { getLinkOrThrow } from "@/lib/api/links/get-link-or-throw";
import { withWorkspace } from "@/lib/auth";
import { verifyFolderAccess } from "@/lib/folder/permissions";
import { getLinkInfoQuerySchema } from "@/lib/zod/schemas/links";
import { getLinkInfoQuerySchemaExtended } from "@/lib/zod/schemas/links";
import { prisma } from "@dub/prisma";
import { NextResponse } from "next/server";

// GET /api/links/info – get the info for a link
export const GET = withWorkspace(
async ({ headers, searchParams, workspace, session }) => {
const { domain, key, linkId, externalId } =
getLinkInfoQuerySchema.parse(searchParams);
const { domain, key, linkId, externalId, includeWebhooks } =
getLinkInfoQuerySchemaExtended.parse(searchParams);

if (!domain && !key && !linkId && !externalId) {
throw new DubApiError({
Expand All @@ -28,6 +28,7 @@ export const GET = withWorkspace(
externalId,
domain,
key,
includeWebhooks,
});

if (link.folderId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,14 @@ export const GET = withPartnerProfile(
});

const response = events.map((event) => {
// don't return ip address for partner profile
// @ts-ignore – ip is deprecated but present in the data
const { ip, click, ...eventRest } = event;
const { ip: _, ...clickRest } = click;

return {
...event,
...eventRest,
click: clickRest,
link: event?.link ? PartnerProfileLinkSchema.parse(event.link) : null,
// @ts-expect-error - customer is not always present
...(event?.customer && {
Expand Down
6 changes: 3 additions & 3 deletions apps/web/lib/api/links/bulk-update-links.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isStored, storage } from "@/lib/storage";
import { isNotHostedImage, storage } from "@/lib/storage";
import z from "@/lib/zod";
import { bulkUpdateLinksBodySchema } from "@/lib/zod/schemas/links";
import { prisma } from "@dub/prisma";
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function bulkUpdateLinks(
title: truncate(title, 120),
description: truncate(description, 240),
image:
proxy && image && !isStored(image)
proxy && image && isNotHostedImage(image)
? `${R2_URL}/images/${linkIds[0]}_${imageUrlNonce}`
: image,
expiresAt: expiresAt ? new Date(expiresAt) : null,
Expand Down Expand Up @@ -129,7 +129,7 @@ export async function bulkUpdateLinks(
// if proxy is true and image is not stored in R2, upload image to R2
proxy &&
image &&
!isStored(image) &&
isNotHostedImage(image) &&
storage.upload(`images/${linkIds[0]}_${imageUrlNonce}`, image, {
width: 1200,
height: 630,
Expand Down
12 changes: 7 additions & 5 deletions apps/web/lib/api/links/create-link.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { qstash } from "@/lib/cron";
import { getPartnerAndDiscount } from "@/lib/planetscale/get-partner-discount";
import { isStored, storage } from "@/lib/storage";
import { isNotHostedImage, storage } from "@/lib/storage";
import { recordLink } from "@/lib/tinybird";
import { ProcessedLinkProps } from "@/lib/types";
import { propagateWebhookTriggerChanges } from "@/lib/webhook/update-webhook";
Expand Down Expand Up @@ -60,7 +60,7 @@ export async function createLink(link: ProcessedLinkProps) {
title: truncate(title, 120),
description: truncate(description, 240),
// if it's an uploaded image, make this null first because we'll update it later
image: proxy && image && !isStored(image) ? null : image,
image: proxy && image && isNotHostedImage(image) ? null : image,
utm_source,
utm_medium,
utm_campaign,
Expand Down Expand Up @@ -150,8 +150,8 @@ export async function createLink(link: ProcessedLinkProps) {
// record link in Tinybird
recordLink(response),
// Upload image to R2 and update the link with the uploaded image URL when
// proxy is enabled and image is set and not stored in R2
...(proxy && image && !isStored(image)
// proxy is enabled and image is set and is not a hosted image URL
...(proxy && image && isNotHostedImage(image)
? [
// upload image to R2
storage.upload(`images/${response.id}`, image, {
Expand Down Expand Up @@ -199,6 +199,8 @@ export async function createLink(link: ProcessedLinkProps) {
...transformLink(response),
// optimistically set the image URL to the uploaded image URL
image:
proxy && image && !isStored(image) ? uploadedImageUrl : response.image,
proxy && image && isNotHostedImage(image)
? uploadedImageUrl
: response.image,
};
}
15 changes: 14 additions & 1 deletion apps/web/lib/api/links/get-link-or-throw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ interface GetLinkParams {
externalId?: string;
domain?: string;
key?: string;
includeWebhooks?: boolean;
}

// Get link or throw error if not found or doesn't belong to workspace
export const getLinkOrThrow = async (params: GetLinkParams) => {
let { workspaceId, domain, key, externalId } = params;
let {
workspaceId,
domain,
key,
externalId,
includeWebhooks = false,
} = params;
let link: Link | null = null;

const linkId = params.linkId || params.externalId || undefined;
Expand All @@ -39,6 +46,9 @@ export const getLinkOrThrow = async (params: GetLinkParams) => {
}
: { id: linkId }),
},
include: {
webhooks: includeWebhooks,
},
});
}

Expand All @@ -56,6 +66,9 @@ export const getLinkOrThrow = async (params: GetLinkParams) => {
key,
},
},
include: {
webhooks: includeWebhooks,
},
});
}

Expand Down
6 changes: 3 additions & 3 deletions apps/web/lib/api/links/update-link.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getPartnerAndDiscount } from "@/lib/planetscale/get-partner-discount";
import { isStored, storage } from "@/lib/storage";
import { isNotHostedImage, storage } from "@/lib/storage";
import { recordLink } from "@/lib/tinybird";
import { LinkProps, ProcessedLinkProps } from "@/lib/types";
import { propagateWebhookTriggerChanges } from "@/lib/webhook/update-webhook";
Expand Down Expand Up @@ -93,7 +93,7 @@ export async function updateLink({
title: truncate(title, 120),
description: truncate(description, 240),
image:
proxy && image && !isStored(image)
proxy && image && isNotHostedImage(image)
? `${R2_URL}/images/${id}_${imageUrlNonce}`
: image,
utm_source: utm_source || null,
Expand Down Expand Up @@ -188,7 +188,7 @@ export async function updateLink({
// if proxy is true and image is not stored in R2, upload image to R2
proxy &&
image &&
!isStored(image) &&
isNotHostedImage(image) &&
storage.upload(`images/${id}_${imageUrlNonce}`, image, {
width: 1200,
height: 630,
Expand Down
4 changes: 4 additions & 0 deletions apps/web/lib/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,7 @@ export const storage = new StorageClient();
export const isStored = (url: string) => {
return url.startsWith(R2_URL) || url.startsWith(OG_AVATAR_URL);
};

export const isNotHostedImage = (imageString: string) => {
return !imageString.startsWith("https://");
};
Empty file removed apps/web/lib/swr/use-link-info.ts
Empty file.
7 changes: 6 additions & 1 deletion apps/web/lib/swr/use-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export default function useLink(
linkIdOrLink &&
(typeof linkIdOrLink === "string"
? `/api/links/${linkIdOrLink}?workspaceId=${workspaceId}`
: `/api/links/info?domain=${linkIdOrLink.domain}&key=${linkIdOrLink.slug}&workspaceId=${workspaceId}`),
: `/api/links/info?${new URLSearchParams({
workspaceId,
domain: linkIdOrLink.domain,
key: linkIdOrLink.slug,
includeWebhooks: "true",
})}`),
fetcher,
swrOptions,
);
Expand Down
7 changes: 7 additions & 0 deletions apps/web/lib/zod/schemas/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ export const getLinkInfoQuerySchema = domainKeySchema.partial().merge(
.openapi({ example: "123456" }),
}),
);

export const getLinksQuerySchemaExtended = getLinksQuerySchemaBase.merge(
z.object({
// Only Dub UI uses the following query parameters
Expand All @@ -732,6 +733,12 @@ export const getLinksQuerySchemaExtended = getLinksQuerySchemaBase.merge(
}),
);

export const getLinkInfoQuerySchemaExtended = getLinkInfoQuerySchema.merge(
z.object({
includeWebhooks: booleanQuerySchema.default("false"),
}),
);

export const linkEventSchema = LinkSchema.extend({
// here we use string because url can be empty
url: z.string(),
Expand Down
68 changes: 36 additions & 32 deletions apps/web/ui/analytics/events/events-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,38 +429,42 @@ export default function EventsTable({
</div>
),
},
{
id: "ip",
header: "IP Address",
accessorKey: "click.ip",
cell: ({ getValue }) =>
getValue() ? (
<span className="truncate" title={getValue()}>
{getValue()}
</span>
) : (
<Tooltip content="We do not record IP addresses for EU users.">
<span className="cursor-default truncate underline decoration-dotted">
Unknown
</span>
</Tooltip>
),
},
// Sale invoice ID
{
id: "invoiceId",
header: "Invoice ID",
accessorKey: "sale.invoiceId",
maxSize: 200,
cell: ({ getValue }) =>
getValue() ? (
<span className="truncate" title={getValue()}>
{getValue()}
</span>
) : (
<span className="text-neutral-400">-</span>
),
},
...(partnerPage
? []
: [
{
id: "ip",
header: "IP Address",
accessorKey: "click.ip",
cell: ({ getValue }) =>
getValue() ? (
<span className="truncate" title={getValue()}>
{getValue()}
</span>
) : (
<Tooltip content="We do not record IP addresses for EU users.">
<span className="cursor-default truncate underline decoration-dotted">
Unknown
</span>
</Tooltip>
),
},
// Sale invoice ID
{
id: "invoiceId",
header: "Invoice ID",
accessorKey: "sale.invoiceId",
maxSize: 200,
cell: ({ getValue }) =>
getValue() ? (
<span className="truncate" title={getValue()}>
{getValue()}
</span>
) : (
<span className="text-neutral-400">-</span>
),
},
]),
// Menu
{
id: "menu",
Expand Down