Skip to content

Commit

Permalink
Merge branch 'main' into customers
Browse files Browse the repository at this point in the history
  • Loading branch information
steven-tey authored Oct 14, 2024
2 parents 49c1be7 + 38ad273 commit 8da7ee3
Show file tree
Hide file tree
Showing 82 changed files with 1,116 additions and 511 deletions.
2 changes: 1 addition & 1 deletion apps/web/app/[domain]/placeholder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function PlaceholderContent() {
</motion.p>
<motion.a
variants={STAGGER_CHILD_VARIANTS}
href={createHref("/", domain, {
href={createHref("/home", domain, {
utm_source: "Custom Domain",
utm_medium: "Welcome Page",
utm_campaign: domain,
Expand Down
8 changes: 1 addition & 7 deletions apps/web/app/api/auth/account-exists/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isWhitelistedEmail } from "@/lib/edge-config";
import { DATABASE_URL, conn } from "@/lib/planetscale";
import { conn } from "@/lib/planetscale";
import { ratelimit } from "@/lib/upstash";
import { ipAddress } from "@vercel/functions";
import { NextRequest, NextResponse } from "next/server";
Expand All @@ -15,12 +15,6 @@ export async function POST(req: NextRequest) {

const { email } = (await req.json()) as { email: string };

if (!DATABASE_URL) {
return new Response("Database connection not established", {
status: 500,
});
}

if (!process.env.NEXT_PUBLIC_IS_DUB) {
return NextResponse.json({ accountExists: true, hasPassword: true });
}
Expand Down
3 changes: 3 additions & 0 deletions apps/web/app/api/domains/[domain]/primary/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ export const POST = withWorkspace(
data: {
primary: true,
},
include: {
registeredDomain: true,
},
}),

// Set all other domains as not primary
Expand Down
5 changes: 5 additions & 0 deletions apps/web/app/api/domains/[domain]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const PATCH = withWorkspace(
slug: newDomain,
placeholder,
expiredUrl,
notFoundUrl,
archived,
} = updateDomainBodySchema.parse(await parseRequestBody(req));

Expand Down Expand Up @@ -88,8 +89,12 @@ export const PATCH = withWorkspace(
...(placeholder && { placeholder }),
...(workspace.plan != "free" && {
expiredUrl,
notFoundUrl,
}),
},
include: {
registeredDomain: true,
},
});

waitUntil(
Expand Down
58 changes: 48 additions & 10 deletions apps/web/app/api/domains/route.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { addDomainToVercel, validateDomain } from "@/lib/api/domains";
import { DubApiError, exceededLimitError } from "@/lib/api/errors";
import { createLink } from "@/lib/api/links";
import { createLink, transformLink } from "@/lib/api/links";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
DomainSchema,
createDomainBodySchema,
getDomainsQuerySchema,
getDomainsQuerySchemaExtended,
} from "@/lib/zod/schemas/domains";
import { DEFAULT_LINK_PROPS } from "@dub/utils";
import { NextResponse } from "next/server";
import { z } from "zod";

// GET /api/domains – get all domains for a workspace
export const GET = withWorkspace(
async ({ workspace, searchParams }) => {
const { search, archived, page, pageSize } =
getDomainsQuerySchema.parse(searchParams);
const { search, archived, page, pageSize, includeLink } =
getDomainsQuerySchemaExtended.parse(searchParams);

const domains = await prisma.domain.findMany({
where: {
Expand All @@ -31,12 +30,43 @@ export const GET = withWorkspace(
},
include: {
registeredDomain: true,
...(includeLink && {
links: {
where: {
key: "_root",
},
include: {
tags: {
select: {
tag: {
select: {
id: true,
name: true,
color: true,
},
},
},
},
},
},
}),
},
take: pageSize,
skip: (page - 1) * pageSize,
});

return NextResponse.json(z.array(DomainSchema).parse(domains));
const response = domains.map((domain) => ({
...DomainSchema.parse(domain),
...(includeLink &&
domain.links.length > 0 && {
link: transformLink({
...domain.links[0],
tags: domain.links[0]["tags"].map((tag) => tag),
}),
}),
}));

return NextResponse.json(response);
},
{
requiredPermissions: ["domains.read"],
Expand All @@ -47,7 +77,7 @@ export const GET = withWorkspace(
export const POST = withWorkspace(
async ({ req, workspace, session }) => {
const body = await parseRequestBody(req);
const { slug, placeholder, expiredUrl } =
const { slug, placeholder, expiredUrl, notFoundUrl } =
createDomainBodySchema.parse(body);

const totalDomains = await prisma.domain.count({
Expand Down Expand Up @@ -102,9 +132,11 @@ export const POST = withWorkspace(
...(placeholder && { placeholder }),
...(workspace.plan !== "free" && {
expiredUrl,
notFoundUrl,
}),
},
}),

createLink({
...DEFAULT_LINK_PROPS,
domain: slug,
Expand All @@ -116,9 +148,15 @@ export const POST = withWorkspace(
}),
]);

return NextResponse.json(DomainSchema.parse(domainRecord), {
status: 201,
});
return NextResponse.json(
DomainSchema.parse({
...domainRecord,
registeredDomain: null,
}),
{
status: 201,
},
);
},
{
requiredPermissions: ["domains.write"],
Expand Down
138 changes: 97 additions & 41 deletions apps/web/app/api/links/bulk/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { DubApiError, exceededLimitError } from "@/lib/api/errors";
import { bulkCreateLinks, combineTagIds, processLink } from "@/lib/api/links";
import {
bulkCreateLinks,
checkIfLinksHaveTags,
checkIfLinksHaveWebhooks,
combineTagIds,
processLink,
} from "@/lib/api/links";
import { bulkDeleteLinks } from "@/lib/api/links/bulk-delete-links";
import { bulkUpdateLinks } from "@/lib/api/links/bulk-update-links";
import { throwIfLinksUsageExceeded } from "@/lib/api/links/usage-checks";
Expand Down Expand Up @@ -86,49 +92,99 @@ export const POST = withWorkspace(
code,
}));

// filter out tags that don't belong to the workspace
const workspaceTags = await prisma.tag.findMany({
where: {
projectId: workspace.id,
},
select: {
id: true,
name: true,
},
});
const workspaceTagIds = workspaceTags.map(({ id }) => id);
const workspaceTagNames = workspaceTags.map(({ name }) => name);
validLinks.forEach((link, index) => {
const combinedTagIds =
combineTagIds({
tagId: link.tagId,
tagIds: link.tagIds,
}) ?? [];
const invalidTagIds = combinedTagIds.filter(
(id) => !workspaceTagIds.includes(id),
);
if (invalidTagIds.length > 0) {
// remove link from validLinks and add error to errorLinks
validLinks = validLinks.filter((_, i) => i !== index);
errorLinks.push({
error: `Invalid tagIds detected: ${invalidTagIds.join(", ")}`,
code: "unprocessable_entity",
link,
});
}
if (checkIfLinksHaveTags(validLinks)) {
// filter out tags that don't belong to the workspace
const tagIds = validLinks
.map((link) =>
combineTagIds({ tagId: link.tagId, tagIds: link.tagIds }),
)
.flat()
.filter(Boolean) as string[];
const tagNames = validLinks
.map((link) => link.tagNames)
.flat()
.filter(Boolean) as string[];

const workspaceTags = await prisma.tag.findMany({
where: {
projectId: workspace.id,
...(tagIds.length > 0 ? { id: { in: tagIds } } : {}),
...(tagNames.length > 0 ? { name: { in: tagNames } } : {}),
},
select: {
id: true,
name: true,
},
});
const workspaceTagIds = workspaceTags.map(({ id }) => id);
const workspaceTagNames = workspaceTags.map(({ name }) => name);
validLinks.forEach((link, index) => {
const combinedTagIds =
combineTagIds({
tagId: link.tagId,
tagIds: link.tagIds,
}) ?? [];
const invalidTagIds = combinedTagIds.filter(
(id) => !workspaceTagIds.includes(id),
);
if (invalidTagIds.length > 0) {
// remove link from validLinks and add error to errorLinks
validLinks = validLinks.filter((_, i) => i !== index);
errorLinks.push({
error: `Invalid tagIds detected: ${invalidTagIds.join(", ")}`,
code: "unprocessable_entity",
link,
});
}

const invalidTagNames = link.tagNames?.filter(
(name) => !workspaceTagNames.includes(name),
);
if (invalidTagNames?.length) {
validLinks = validLinks.filter((_, i) => i !== index);
errorLinks.push({
error: `Invalid tagNames detected: ${invalidTagNames.join(", ")}`,
code: "unprocessable_entity",
link,
const invalidTagNames = link.tagNames?.filter(
(name) => !workspaceTagNames.includes(name),
);
if (invalidTagNames?.length) {
validLinks = validLinks.filter((_, i) => i !== index);
errorLinks.push({
error: `Invalid tagNames detected: ${invalidTagNames.join(", ")}`,
code: "unprocessable_entity",
link,
});
}
});
}

if (checkIfLinksHaveWebhooks(validLinks)) {
if (workspace.plan === "free" || workspace.plan === "pro") {
throw new DubApiError({
code: "forbidden",
message:
"You can only use webhooks on a Business plan and above. Upgrade to Business to use this feature.",
});
}
});

const webhookIds = validLinks
.map((link) => link.webhookIds)
.flat()
.filter((id): id is string => id !== null);

const webhooks = await prisma.webhook.findMany({
where: { projectId: workspace.id, id: { in: webhookIds } },
});

const workspaceWebhookIds = webhooks.map(({ id }) => id);

validLinks.forEach((link, index) => {
const invalidWebhookIds = link.webhookIds?.filter(
(id) => !workspaceWebhookIds.includes(id),
);
if (invalidWebhookIds && invalidWebhookIds.length > 0) {
validLinks = validLinks.filter((_, i) => i !== index);
errorLinks.push({
error: `Invalid webhookIds detected: ${invalidWebhookIds.join(", ")}`,
code: "unprocessable_entity",
link,
});
}
});
}

const validLinksResponse =
validLinks.length > 0 ? await bulkCreateLinks({ links: validLinks }) : [];
Expand Down
2 changes: 2 additions & 0 deletions apps/web/app/api/links/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const GET = withWorkspace(
showArchived,
withTags,
includeUser,
includeWebhooks,
linkIds,
} = getLinksQuerySchemaExtended.parse(searchParams);

Expand All @@ -52,6 +53,7 @@ export const GET = withWorkspace(
showArchived,
withTags,
includeUser,
includeWebhooks,
linkIds,
});

Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/api/tags/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const GET = withWorkspace(
},
);

// POST /api/workspaces/[idOrSlug]/tags - create a tag for a workspace
// POST /api/tags - create a tag for a workspace
export const POST = withWorkspace(
async ({ req, workspace, headers }) => {
const tagsCount = await prisma.tag.count({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export default function WorkspaceDomainsClient() {

const [openPopover, setOpenPopover] = useState(false);
const { searchParams, queryParams } = useRouterStuff();
const { allWorkspaceDomains, loading } = useDomains({ includeParams: true });
const { allWorkspaceDomains, loading } = useDomains({
opts: { includeLink: "true" },
});
const { data: domainsCount } = useDomainsCount();

const { pagination, setPagination } = usePagination(DOMAINS_MAX_PAGE_SIZE);
Expand Down
Loading

0 comments on commit 8da7ee3

Please sign in to comment.