Skip to content
This repository has been archived by the owner on Aug 7, 2024. It is now read-only.

Feat custom domain #9436

Merged
merged 37 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d2853d8
feat: use custom domain
eddiejaoude Oct 9, 2023
7a69b23
feat: custom domain
eddiejaoude Oct 9, 2023
d55da15
fix: profile model domain property
eddiejaoude Oct 10, 2023
b2e9f81
Merge branch 'main' into feat-custom-domain
eddiejaoude Oct 10, 2023
2946452
feat: allow user to set custom domain
eddiejaoude Oct 10, 2023
7d0c22f
fix: efficiency improvements to middleware
eddiejaoude Oct 11, 2023
9259ef7
fix: domain not used
eddiejaoude Oct 11, 2023
c37edc6
fix: default value for domain
eddiejaoude Oct 11, 2023
cf5f4c1
fix: deploy custom domain to preview
eddiejaoude Oct 12, 2023
9e6061f
fix: hardcoded middleware domain
eddiejaoude Oct 12, 2023
23514b4
fix: debug code for middleware
eddiejaoude Oct 12, 2023
35d3d2e
fix: hostname and domain in middleware
eddiejaoude Oct 12, 2023
593b165
fix: hostname replace all
eddiejaoude Oct 12, 2023
0cc8e96
fix: domain api search
eddiejaoude Oct 13, 2023
1614504
fix: debugging domain api search
eddiejaoude Oct 13, 2023
7d9a1e3
fix: manage premium page form
eddiejaoude Oct 13, 2023
fc6d715
wip: add custom domain to vercel
eddiejaoude Oct 14, 2023
7533de5
merged 'main' into 'feat-custom-domain'
eddiejaoude Oct 24, 2023
51f3448
Merge branch 'main' into feat-custom-domain
eddiejaoude Oct 25, 2023
71e38e6
feat: premium domain add to vercel
eddiejaoude Oct 25, 2023
4dfbe11
feat: custom domain to team + project
eddiejaoude Oct 26, 2023
e773195
Merge branch 'main' into feat-custom-domain
eddiejaoude Nov 3, 2023
e2026bc
fix: remove custom url protocols
eddiejaoude Nov 4, 2023
05fdaff
fix: extra custom domain check
eddiejaoude Nov 4, 2023
24954a7
fix: custom domain ignore www
eddiejaoude Nov 4, 2023
2ac2ce8
feat: hide nav+footer if custom domain
eddiejaoude Nov 4, 2023
c38cfa5
fix: custom domain vercel error handling
eddiejaoude Nov 5, 2023
0095774
docs: improve premium docs for dns
eddiejaoude Nov 5, 2023
cb5a6f9
fix: extra debugging for custom domain
eddiejaoude Nov 6, 2023
2211e1c
fix: more debugging for custom domain
eddiejaoude Nov 6, 2023
464c84b
fix: even more debugging for custom domain
eddiejaoude Nov 6, 2023
9ed22cf
fix: middleware rewrite condition
eddiejaoude Nov 6, 2023
f1ea98b
fix: www in custom base domain
eddiejaoude Nov 6, 2023
a60cd72
docs: custom domain setup
eddiejaoude Nov 6, 2023
18c10bc
feat: custom domain vercel status
eddiejaoude Nov 6, 2023
5b641dd
fix: logging when there are errors
eddiejaoude Nov 6, 2023
3c93048
docs: changelog feeature
eddiejaoude Nov 8, 2023
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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ STRIPE_SECRET_KEY=""
STRIPE_PREMIUM_PRICING_ID=""
STRIPE_WEBHOOK_SECRET=""
NEXT_PUBLIC_PREMIUM_SUPPORT_URL=""

VERCEL_PROJECT_ID=""
VERCEL_TEAM_ID=""
VERCEL_AUTH_TOKEN=""
2 changes: 1 addition & 1 deletion .github/workflows/vercel-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
workflow_dispatch:
push:
branches:
- feat-middleware
- feat-custom-domain

jobs:
deploy:
Expand Down
21 changes: 11 additions & 10 deletions components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,22 @@ import Link from "./Link";

export default function Button({
primary = false,
disable,
disabled = false,
className,
overrideClassNames = false,
children,
...restProps
}) {
let defaultClassName =
"w-full inline-flex items-center flex-1 justify-center rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out";
!disable
? (defaultClassName += primary
let defaultClassName = classNames(
"w-full inline-flex items-center flex-1 justify-center rounded-md border-2 border-primary-high dark:border-white hover:border-transparent px-5 py-3 text-base font-medium first-letter:bg-white transition duration-400 ease-in-out",
!disabled
? primary
? " text-primary-medium bg-secondary-medium hover:bg-tertiary-medium"
: " text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium")
: (defaultClassName += disable
? " border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed "
: " cursor-pointer");
: " text-secondary-high dark:text-secondary-high-high hover:text-white dark:hover:text-white dark:bg-primary-low hover:bg-secondary-medium dark:hover:bg-secondary-medium"
: disabled
? " border-2 border-red border shadow-sm bg-primary-low text-primary-medium cursor-not-allowed "
: " cursor-pointer",
);

const link = (
<Link
Expand All @@ -36,7 +37,7 @@ export default function Button({
className={
overrideClassNames ? className : classNames(defaultClassName, className)
}
disabled={disable}
disabled={disabled}
{...restProps}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions components/form/Input.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const Input = forwardRef(
id={name}
name={name}
value={value}
disabled={disabled}
onKeyDown={handleKeydown}
{...restProps}
/>
Expand Down
13 changes: 13 additions & 0 deletions components/layouts/DocsLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ export const navigation = [
name: "GitHub Repos with Forms",
href: "/docs/how-to-guides/repos-forms",
},
{
name: "Premium Features",
href: "/docs/how-to-guides/premium",
},
],
},
{
Expand Down Expand Up @@ -100,6 +104,15 @@ export const navigation = [
{ name: "Hacktoberfest", href: "/docs/contributing/hacktoberfest" },
],
},
{
name: "Premium",
// icon: ChartPieIcon,
children: [
{ name: "Auto", href: "/docs/premium/auto" },
{ name: "Customisation", href: "/docs/premium/customisation" },
{ name: "Custom Domain", href: "/docs/premium/domain" },
],
},
{
name: "Other",
// icon: ChartPieIcon,
Expand Down
3 changes: 3 additions & 0 deletions config/schemas/serverSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const envSchema = z.object({
NEXT_PUBLIC_VERCEL_ENV: z.string().optional(),
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
VERCEL_PROJECT_ID: z.string().optional(),
VERCEL_TEAM_ID: z.string().optional(),
VERCEL_AUTH_TOKEN: z.string().optional(),
});

const serverEnv = envSchema.safeParse(process.env);
Expand Down
56 changes: 55 additions & 1 deletion middleware.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";

// note: logger is not available in middleware, using console.log instead

export const config = {
matcher: [
"/",

// account management
"/account/:path*",
"/api/account/:path*",
Expand All @@ -14,10 +18,60 @@ export const config = {
};

export async function middleware(req) {
const protocol = process.env.NODE_ENV === "development" ? "http" : "https";
const hostname = req.headers.get("host");
const reqPathName = req.nextUrl.pathname;
const sessionRequired = ["/account", "/api/account"];
const adminRequired = ["/admin", "/api/admin"];
const adminUsers = process.env.ADMIN_USERS.split(",");
const reqPathName = req.nextUrl.pathname;
const hostedDomain = process.env.NEXT_PUBLIC_BASE_URL.replace(
/http:\/\/|https:\/\//,
"",
);
const hostedDomains = [hostedDomain, `www.${hostedDomain}`];

// if custom domain + on root path
if (!hostedDomains.includes(hostname) && reqPathName === "/") {
console.log(`custom domain used: "${hostname}"`);

let res;
let profile;
let url = `${
process.env.NEXT_PUBLIC_BASE_URL
}/api/search/${encodeURIComponent(hostname)}`;
try {
res = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
profile = await res.json();
} catch (e) {
console.error(url, e);
return NextResponse.error(e);
}

if (
profile?.username &&
profile.user.type === "premium" &&
profile.settings?.domain &&
profile.settings.domain === hostname
) {
console.log(
`custom domain matched "${hostname}" for username "${profile.username}" (protocol: "${protocol}")`,
);
// if match found rewrite to custom domain and display profile page
return NextResponse.rewrite(
new URL(
`/${profile.username}`,
`${protocol}://${profile.settings.domain}`,
),
);
}

console.error(`custom domain NOT matched "${hostname}"`);
}

// if not in sessionRequired or adminRequired, skip
if (
Expand Down
16 changes: 14 additions & 2 deletions models/Profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,22 @@ const ProfileSchema = new Schema(
type: Boolean,
default: false,
},
domain: {
type: String,
default: "",
get: (v) => v.replaceAll("|", "."),
set: (v) => v.replaceAll(".", "|"),
validator: function (v) {
return /^[^https?:\/\/](?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}$/.test(
v,
);
},
message: (props) => `${props.value} is not a valid domain!`,
},
},
},
{ timestamps: true },
{ timestamps: true, toJSON: { getters: true } },
);

module.exports =
mongoose.models.Profile || mongoose.model("Profile", ProfileSchema);
mongoose.models?.Profile || mongoose.model("Profile", ProfileSchema);
9 changes: 9 additions & 0 deletions pages/[username].js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export async function getServerSideProps(context) {
profile.cleanBio = profile.bio;
}

// override hiding navbar and footer if custom domain matches
if (
profile.settings?.domain &&
profile.settings.domain.replaceAll("|", ".") === req.headers.host
) {
profile.settings.hideNavbar = true;
profile.settings.hideFooter = true;
}

return {
props: {
data: profile,
Expand Down
2 changes: 1 addition & 1 deletion pages/account/manage/links.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export default function ManageLinks({ BASE_URL, username, links }) {
{!reorder && (
<Button
onClick={() => setReorder(true)}
disable={linkList.length < 2}
disabled={linkList.length < 2}
>
<ArrowPathIcon className="h-5 w-5 mr-2" />
REORDER
Expand Down
Loading