Skip to content

Commit

Permalink
Offload link creation and editing side effects to qstash
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey committed Jan 21, 2024
1 parent 92e6c05 commit 5445e54
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 104 deletions.
85 changes: 85 additions & 0 deletions apps/web/app/api/cron/links/event/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { qstash, receiver } from "@/lib/cron";
import prisma from "@/lib/prisma";
import {
APP_DOMAIN_WITH_NGROK,
GOOGLE_FAVICON_URL,
getApexDomain,
log,
} from "@dub/utils";

export async function POST(req: Request) {
const body = await req.json();
if (process.env.VERCEL === "1") {
const isValid = await receiver.verify({
signature: req.headers.get("Upstash-Signature") || "",
body: JSON.stringify(body),
});
if (!isValid) {
return new Response("Unauthorized", { status: 401 });
}
}

const { linkId, type } = body as {
linkId: string;
type: "create" | "edit";
};

const link = await prisma.link.findUnique({
where: {
id: linkId,
},
});

if (!link) {
return new Response("Link not found", { status: 200 });
}

console.log(`Received ${type} event for link ${linkId}`);

// if the link is a dub.sh link, do some checks
if (link.domain === "dub.sh") {
const invalidFavicon = await fetch(
`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`,
).then((res) => !res.ok);

if (invalidFavicon) {
await log({
message: `Suspicious link detected: ${link.domain}/${link.key}${link.url}`,
type: "links",
mention: true,
});
}
}

// for public links, delete after 30 mins (if not claimed)
if (!link.userId) {
await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/delete`,
// delete after 30 mins
delay: 30 * 60,
body: {
linkId,
},
});
}

// increment links usage
if (type === "create" && link.projectId) {
const updatedProject = await prisma.project.update({
where: {
id: link.projectId,
},
data: {
linksUsage: {
increment: 1,
},
},
});
console.log({ updatedProject });
if (updatedProject.linksUsage > updatedProject.linksLimit) {
// send email
}
}

return new Response("OK", { status: 200 });
}
43 changes: 1 addition & 42 deletions apps/web/app/api/cron/links/verify/route.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1 @@
import { receiver } from "@/lib/cron";
import prisma from "@/lib/prisma";
import { GOOGLE_FAVICON_URL, getApexDomain, log } from "@dub/utils";

export async function POST(req: Request) {
const body = await req.json();
if (process.env.VERCEL === "1") {
const isValid = await receiver.verify({
signature: req.headers.get("Upstash-Signature") || "",
body: JSON.stringify(body),
});
if (!isValid) {
return new Response("Unauthorized", { status: 401 });
}
}

const { linkId } = body;

const link = await prisma.link.findUnique({
where: {
id: linkId,
},
});

if (!link) {
return new Response("Link not found", { status: 200 });
}

const invalidFavicon = await fetch(
`${GOOGLE_FAVICON_URL}${getApexDomain(link.url)}`,
).then((res) => !res.ok);

if (invalidFavicon) {
await log({
message: `Suspicious link detected: ${link.domain}/${link.key}${link.url}`,
type: "links",
mention: true,
});
}

return new Response("OK", { status: 200 });
}
export { POST } from "../event/route";
2 changes: 1 addition & 1 deletion apps/web/app/api/cron/verify/route.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { POST } from "../links/verify/route";
export { POST } from "../links/event/route";
19 changes: 7 additions & 12 deletions apps/web/app/api/links/[linkId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,13 @@ export const PUT = withAuth(async ({ req, headers, project, link }) => {
key: link!.key,
updatedLink: processedLink,
}),
...(link &&
processedLink.domain === "dub.sh" &&
processedLink.url !== link.url
? [
qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/verify`,
body: {
linkId: link.id,
},
}),
]
: []),
qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/event`,
body: {
linkId: link!.id,
type: "edit",
},
}),
// @ts-ignore
]).then((results) => results.map((result) => result.value));

Expand Down
47 changes: 18 additions & 29 deletions apps/web/app/api/links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,8 @@ export const POST = withAuth(
return new Response("Missing or invalid body.", { status: 400, headers });
}

const { link, error, status } = await processLink({
payload: body,
project,
session,
});

if (error) {
return new Response(error, { status, headers });
}

if (!session) {
console.log("ratelimiting");
const ip = req.headers.get("x-forwarded-for") || LOCALHOST_IP;
const { success } = await ratelimit(10, "1 d").limit(ip);

Expand All @@ -64,6 +55,16 @@ export const POST = withAuth(
}
}

const { link, error, status } = await processLink({
payload: body,
project,
session,
});

if (error) {
return new Response(error, { status, headers });
}

const response = await addLink(link);

if (response === null) {
Expand All @@ -73,25 +74,13 @@ export const POST = withAuth(
});
}

await Promise.all([
link.domain === "dub.sh" &&
qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/verify`,
body: {
linkId: response.id,
},
}),
// for public links, delete after 30 mins (if not claimed)
!link.userId &&
qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/delete`,
// delete after 30 mins
delay: 30 * 60,
body: {
linkId: response.id,
},
}),
]);
await qstash.publishJSON({
url: `${APP_DOMAIN_WITH_NGROK}/api/cron/links/event`,
body: {
linkId: response.id,
type: "create",
},
});

return NextResponse.json(response, { headers });
},
Expand Down
21 changes: 2 additions & 19 deletions apps/web/lib/api/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import cloudinary from "cloudinary";
import { isIframeable } from "../middleware/utils";
import { LinkProps, ProjectProps } from "../types";
import { Session } from "../auth";
import { getLinkViaEdge } from "../planetscale";

export async function getLinksForProject({
projectId,
Expand Down Expand Up @@ -194,14 +195,7 @@ export async function checkIfKeyExists(domain: string, key: string) {
return true;
}
}
const link = await prisma.link.findUnique({
where: {
domain_key: {
domain,
key,
},
},
});
const link = await getLinkViaEdge(domain, key);
return !!link;
}

Expand Down Expand Up @@ -470,17 +464,6 @@ export async function addLink(link: LinkProps) {
nx: true,
},
),
link.projectId &&
prisma.project.update({
where: {
id: link.projectId,
},
data: {
linksUsage: {
increment: 1,
},
},
}),
]);

if (proxy && image) {
Expand Down
3 changes: 2 additions & 1 deletion apps/web/lib/middleware/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { SHORT_DOMAIN } from "@dub/utils";
import { NextRequest } from "next/server";

export const parse = (req: NextRequest) => {
let domain = req.headers.get("host") as string;
domain = domain.replace("www.", ""); // remove www. from domain
if (domain === "dub.localhost:8888" || domain.endsWith(".vercel.app")) {
// for local development and preview URLs
domain = "dub.sh";
domain = SHORT_DOMAIN;
}

// path is the path of the URL (e.g. dub.co/stats/github -> /stats/github)
Expand Down

0 comments on commit 5445e54

Please sign in to comment.