Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cal 🤝 Dub #1371

Merged
merged 4 commits into from
Oct 11, 2024
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
2 changes: 0 additions & 2 deletions apps/web/app/api/ai/completion/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 26,6 @@ export const POST = withWorkspaceEdge(
model,
} = completionSchema.parse(await req.json());

console.log({ prompt });

const result = await streamText({
model: anthropic(model),
messages: [
Expand Down
8 changes: 4 additions & 4 deletions apps/web/app/api/cron/import/csv/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,4 @@
import { addDomainToVercel, getDefaultDomains } from "@/lib/api/domains";
import { addDomainToVercel } from "@/lib/api/domains";
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { bulkCreateLinks, createLink, processLink } from "@/lib/api/links";
import { verifyQstashSignature } from "@/lib/cron/verify-qstash";
Expand All @@ -11,6 11,7 @@ import { createLinkBodySchema } from "@/lib/zod/schemas/links";
import { randomBadgeColor } from "@/ui/links/tag-badge";
import {
DEFAULT_LINK_PROPS,
DUB_DOMAINS_ARRAY,
getPrettyUrl,
log,
parseDateTime,
Expand Down Expand Up @@ -69,7 70,7 @@ export async function POST(req: Request) {

const response = await storage.fetch(url);

const [tags, domains, defaultDomains] = await Promise.all([
const [tags, domains] = await Promise.all([
prisma.tag.findMany({
where: { projectId: workspace.id },
select: { name: true },
Expand All @@ -78,7 79,6 @@ export async function POST(req: Request) {
where: { projectId: workspace.id },
select: { slug: true },
}),
getDefaultDomains(workspace.id),
]);

const addedTags: string[] = [];
Expand Down Expand Up @@ -172,7 172,7 @@ export async function POST(req: Request) {
const domainsNotInWorkspace = selectedDomains.filter(
(domain) =>
!domains?.find((d) => d.slug === domain) &&
!defaultDomains.find((d) => d === domain) &&
!DUB_DOMAINS_ARRAY.includes(domain) &&
!addedDomains.includes(domain),
);

Expand Down
40 changes: 36 additions & 4 deletions apps/web/app/api/domains/default/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,3 @@
import { getDefaultDomains } from "@/lib/api/domains";
import { DubApiError } from "@/lib/api/errors";
import { withWorkspace } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
Expand All @@ -11,7 10,39 @@ import { NextResponse } from "next/server";
export const GET = withWorkspace(
async ({ workspace, searchParams }) => {
const { search } = getDefaultDomainsQuerySchema.parse(searchParams);
return NextResponse.json(await getDefaultDomains(workspace.id, { search }));

const data = await prisma.defaultDomains.findUnique({
where: {
projectId: workspace.id,
},
select: {
dubsh: true,
dublink: true,
chatgpt: true,
sptifi: true,
gitnew: true,
callink: true,
amznid: true,
ggllink: true,
figpage: true,
},
});

let defaultDomains: string[] = [];

if (data) {
defaultDomains = Object.keys(data)
.filter((key) => data[key])
.map(
(domain) =>
DUB_DOMAINS_ARRAY.find((d) => d.replace(".", "") === domain)!,
)
.filter((domain) =>
search ? domain?.toLowerCase().includes(search.toLowerCase()) : true,
);
}

return NextResponse.json(defaultDomains);
},
{
requiredPermissions: ["domains.read"],
Expand All @@ -22,8 53,8 @@ const updateDefaultDomainsSchema = z.object({
defaultDomains: z.array(z.enum(DUB_DOMAINS_ARRAY as [string, ...string[]])),
});

// PUT /api/domains/default - edit default domains
export const PUT = withWorkspace(
// PATCH /api/domains/default - edit default domains
export const PATCH = withWorkspace(
async ({ req, workspace }) => {
const { defaultDomains } = await updateDefaultDomainsSchema.parseAsync(
await req.json(),
Expand All @@ -47,6 78,7 @@ export const PUT = withWorkspace(
chatgpt: defaultDomains.includes("chatg.pt"),
sptifi: defaultDomains.includes("spti.fi"),
gitnew: defaultDomains.includes("git.new"),
callink: defaultDomains.includes("cal.link"),
amznid: defaultDomains.includes("amzn.id"),
ggllink: defaultDomains.includes("ggl.link"),
figpage: defaultDomains.includes("fig.page"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 8,7 @@ import { NextRequest, NextResponse } from "next/server";

export const runtime = "edge";

// GET /api/links/verify – run keyChecks on the key
// GET /api/links/exists – run keyChecks on the key
export const GET = async (req: NextRequest) => {
try {
const searchParams = getSearchParams(req.url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 40,7 @@ function DubDomainsIcon(domain: string) {
}

export function DefaultDomains() {
const { id, plan, role } = useWorkspace();
const { id, plan, role, flags } = useWorkspace();
const permissionsError = clientAccessCheck({
action: "domains.write",
role,
Expand Down Expand Up @@ -74,76 74,81 @@ export function DefaultDomains() {
</p>
</div>
<div className="mt-2 grid grid-cols-1 gap-3">
{DUB_DOMAINS.filter((domain) => domain.slug !== "loooooooo.ng").map(
({ slug, description }) => {
return (
<div
key={slug}
className="flex items-center justify-between gap-4 rounded-xl border border-gray-200 bg-white p-5"
>
<DomainCardTitleColumn
domain={slug}
icon={DubDomainsIcon(slug)}
description={description}
defaultDomain
/>
<Switch
disabled={submitting}
disabledTooltip={
permissionsError ||
(slug === "dub.link" && plan === "free" ? (
<TooltipContent
title="You can only use dub.link on a Pro plan and above. Upgrade to Pro to use this domain."
cta="Upgrade to Pro"
href={`/${slug}/upgrade`}
/>
) : undefined)
}
checked={defaultDomains?.includes(slug)}
fn={() => {
const oldDefaultDomains = defaultDomains.slice();
const newDefaultDomains = defaultDomains.includes(slug)
? defaultDomains.filter((d) => d !== slug)
: [...defaultDomains, slug];
{DUB_DOMAINS.filter((domain) => {
if (domain.slug === "cal.link") {
return flags?.callink;
} else if (domain.slug === "loooooooo.ng") {
return false;
}
return true;
}).map(({ slug, description }) => {
return (
<div
key={slug}
className="flex items-center justify-between gap-4 rounded-xl border border-gray-200 bg-white p-5"
>
<DomainCardTitleColumn
domain={slug}
icon={DubDomainsIcon(slug)}
description={description}
defaultDomain
/>
<Switch
disabled={submitting}
disabledTooltip={
permissionsError ||
(slug === "dub.link" && plan === "free" ? (
<TooltipContent
title="You can only use dub.link on a Pro plan and above. Upgrade to Pro to use this domain."
cta="Upgrade to Pro"
href={`/${slug}/upgrade`}
/>
) : undefined)
}
checked={defaultDomains?.includes(slug)}
fn={() => {
const oldDefaultDomains = defaultDomains.slice();
const newDefaultDomains = defaultDomains.includes(slug)
? defaultDomains.filter((d) => d !== slug)
: [...defaultDomains, slug];

setDefaultDomains(newDefaultDomains);
setSubmitting(true);
fetch(`/api/domains/default?workspaceId=${id}`, {
method: "PUT",
body: JSON.stringify({
defaultDomains: newDefaultDomains.filter(
(d) => d !== null,
),
}),
})
.then(async (res) => {
if (res.ok) {
toast.success(
`${slug} ${newDefaultDomains.includes(slug) ? "added to" : "removed from"} default domains.`,
);
await mutate();
setDefaultDomains(newDefaultDomains);
setSubmitting(true);
fetch(`/api/domains/default?workspaceId=${id}`, {
method: "PUT",
body: JSON.stringify({
defaultDomains: newDefaultDomains.filter(
(d) => d !== null,
),
}),
})
.then(async (res) => {
if (res.ok) {
toast.success(
`${slug} ${newDefaultDomains.includes(slug) ? "added to" : "removed from"} default domains.`,
);
await mutate();
} else {
const { error } = await res.json();
if (error.message.includes("Upgrade to Pro")) {
toast.custom(() => (
<UpgradeRequiredToast
title="You've discovered a Pro feature!"
message={error.message}
/>
));
} else {
const { error } = await res.json();
if (error.message.includes("Upgrade to Pro")) {
toast.custom(() => (
<UpgradeRequiredToast
title="You've discovered a Pro feature!"
message={error.message}
/>
));
} else {
toast.error(error.message);
}
setDefaultDomains(oldDefaultDomains);
toast.error(error.message);
}
})
.finally(() => setSubmitting(false));
}}
/>
</div>
);
},
)}
setDefaultDomains(oldDefaultDomains);
}
})
.finally(() => setSubmitting(false));
}}
/>
</div>
);
})}
</div>
</div>
);
Expand Down
37 changes: 0 additions & 37 deletions apps/web/lib/api/domains/get-default-domains.ts

This file was deleted.

1 change: 0 additions & 1 deletion apps/web/lib/api/domains/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 1,6 @@
export * from "./add-domain-vercel";
export * from "./delete-domain-links";
export * from "./get-config-response";
export * from "./get-default-domains";
export * from "./get-domain-response";
export * from "./remove-domain-vercel";
export * from "./utils";
Expand Down
40 changes: 38 additions & 2 deletions apps/web/lib/api/links/process-link.ts
Original file line number Diff line number Diff line change
@@ -1,4 1,8 @@
import { isBlacklistedDomain, updateConfig } from "@/lib/edge-config";
import {
getFeatureFlags,
isBlacklistedDomain,
updateConfig,
} from "@/lib/edge-config";
import { getPangeaDomainIntel } from "@/lib/pangea";
import { checkIfUserExists, getRandomKey } from "@/lib/planetscale";
import { prisma } from "@/lib/prisma";
Expand Down Expand Up @@ -29,7 33,10 @@ export async function processLink<T extends Record<string, any>>({
skipExternalIdChecks = false, // only skip when externalId doesn't change (e.g. when editing a link)
}: {
payload: NewLinkProps & T;
workspace?: Pick<WorkspaceProps, "id" | "plan" | "conversionEnabled">;
workspace?: Pick<
WorkspaceProps,
"id" | "plan" | "conversionEnabled" | "flags"
>;
userId?: string;
bulk?: boolean;
skipKeyChecks?: boolean;
Expand Down Expand Up @@ -199,6 206,35 @@ export async function processLink<T extends Record<string, any>>({
};
}

if (domain === "cal.link") {
const flags = await getFeatureFlags({
workspaceId: workspace?.id,
});
if (!flags?.callink) {
return {
link: payload,
error:
"You can only use the cal.link domain if you have beta access to it. Contact [email protected] to get access.",
code: "forbidden",
};
}
}

if (key?.includes("/")) {
// check if the user has access to the parent link
const parentKey = key.split("/")[0];
const parentLink = await prisma.link.findUnique({
where: { domain_key: { domain, key: parentKey } },
});
if (parentLink?.userId !== userId) {
return {
link: payload,
error: `You do not have access to create links in the ${domain}/${parentKey}/ subdirectory.`,
code: "forbidden",
};
}
}

// else, check if the domain belongs to the workspace
} else if (!domains?.find((d) => d.slug === domain)) {
return {
Expand Down
Loading
Loading