Skip to content

Commit b21d346

Browse files
committed
[Experiment] unstable_cache: Teams and Projects
1 parent c86e13b commit b21d346

File tree

9 files changed

+219
-89
lines changed

9 files changed

+219
-89
lines changed

apps/dashboard/next.config.ts

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { SentryBuildOptions } from "@sentry/nextjs";
21
import type { NextConfig } from "next";
32
import type { RemotePattern } from "next/dist/shared/lib/image-config";
43
import FRAMER_PATHS from "./framer-rewrites";
@@ -82,38 +81,6 @@ function determineIpfsGateways() {
8281
return remotePatterns;
8382
}
8483

85-
const SENTRY_OPTIONS: SentryBuildOptions = {
86-
// For all available options, see:
87-
// https://github.com/getsentry/sentry-webpack-plugin#options
88-
89-
org: "thirdweb-dev",
90-
project: "dashboard",
91-
// An auth token is required for uploading source maps.
92-
authToken: process.env.SENTRY_AUTH_TOKEN,
93-
// Suppresses source map uploading logs during build
94-
silent: true,
95-
// For all available options, see:
96-
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
97-
98-
// Upload a larger set of source maps for prettier stack traces (increases build time)
99-
widenClientFileUpload: true,
100-
101-
// Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load)
102-
tunnelRoute: "/err",
103-
104-
// Hides source maps from generated client bundles
105-
hideSourceMaps: true,
106-
107-
// Automatically tree-shake Sentry logger statements to reduce bundle size
108-
disableLogger: true,
109-
110-
// Enables automatic instrumentation of Vercel Cron Monitors.
111-
// See the following for more information:
112-
// https://docs.sentry.io/product/crons/
113-
// https://vercel.com/docs/cron-jobs
114-
automaticVercelMonitors: false,
115-
};
116-
11784
const baseNextConfig: NextConfig = {
11885
eslint: {
11986
ignoreDuringBuilds: true,
@@ -190,34 +157,28 @@ function getConfig(): NextConfig {
190157
// eslint-disable-next-line @typescript-eslint/no-var-requires
191158
const { withPlausibleProxy } = require("next-plausible");
192159
// eslint-disable-next-line @typescript-eslint/no-var-requires
193-
const { withSentryConfig } = require("@sentry/nextjs");
194160
return withBundleAnalyzer(
195161
withPlausibleProxy({
196162
customDomain: "https://pl.thirdweb.com",
197163
scriptName: "pl",
198-
})(
199-
withSentryConfig(
200-
{
201-
...baseNextConfig,
202-
// @ts-expect-error - this is a valid option
203-
webpack: (config) => {
204-
if (config.cache) {
205-
config.cache = Object.freeze({
206-
type: "memory",
207-
});
208-
}
209-
config.externals.push("pino-pretty");
210-
config.module = {
211-
...config.module,
212-
exprContextCritical: false,
213-
};
214-
// Important: return the modified config
215-
return config;
216-
},
217-
},
218-
SENTRY_OPTIONS,
219-
),
220-
),
164+
})({
165+
...baseNextConfig,
166+
// @ts-expect-error - this is a valid option
167+
webpack: (config) => {
168+
if (config.cache) {
169+
config.cache = Object.freeze({
170+
type: "memory",
171+
});
172+
}
173+
config.externals.push("pino-pretty");
174+
config.module = {
175+
...config.module,
176+
exprContextCritical: false,
177+
};
178+
// Important: return the modified config
179+
return config;
180+
},
181+
}),
221182
);
222183
}
223184
// otherwise return the base

apps/dashboard/public/robots.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# *
22
User-agent: *
3-
Allow: /
3+
Disallow: /
44

55
# Host
66
Host: https://thirdweb.com

apps/dashboard/src/@/actions/proxies.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use server";
2-
2+
import { revalidateTag } from "next/cache";
33
import { getAuthToken } from "../../app/api/lib/getAuthToken";
4+
import { projectCacheTag, teamCacheTag } from "../constants/cacheTags";
45
import { API_SERVER_URL } from "../constants/env";
56

67
type ProxyActionParams = {
@@ -9,6 +10,12 @@ type ProxyActionParams = {
910
method: "GET" | "POST" | "PUT" | "DELETE";
1011
body?: string;
1112
headers?: Record<string, string>;
13+
revalidateCacheTags?: RevalidateCacheTagOptions;
14+
};
15+
16+
type RevalidateCacheTagOptions = {
17+
teamTag?: true;
18+
projectTag?: true;
1219
};
1320

1421
type ProxyActionResult<T extends object> =
@@ -28,6 +35,15 @@ async function proxy<T extends object>(
2835
params: ProxyActionParams,
2936
): Promise<ProxyActionResult<T>> {
3037
const authToken = await getAuthToken();
38+
const { revalidateCacheTags: revalidateCacheTag } = params;
39+
40+
if (!authToken) {
41+
return {
42+
status: 401,
43+
ok: false,
44+
error: "Unauthorized",
45+
};
46+
}
3147

3248
// build URL
3349
const url = new URL(baseUrl);
@@ -64,6 +80,16 @@ async function proxy<T extends object>(
6480
}
6581
}
6682

83+
if (revalidateCacheTag) {
84+
if (revalidateCacheTag.teamTag) {
85+
revalidateTag(teamCacheTag(authToken));
86+
}
87+
88+
if (revalidateCacheTag.projectTag) {
89+
revalidateTag(projectCacheTag(authToken));
90+
}
91+
}
92+
6793
return {
6894
status: res.status,
6995
ok: true,

apps/dashboard/src/@/api/projects.ts

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import "server-only";
22
import { API_SERVER_URL } from "@/constants/env";
3+
import { unstable_cache } from "next/cache";
34
import { getAuthToken } from "../../app/api/lib/getAuthToken";
5+
import { projectCacheTag } from "../constants/cacheTags";
46

57
export type Project = {
68
id: string;
@@ -26,37 +28,74 @@ export async function getProjects(teamSlug: string) {
2628
return [];
2729
}
2830

29-
const teamsRes = await fetch(
30-
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`,
31+
const getCachedProjects = unstable_cache(
32+
getProjectsForAuthToken,
33+
["getProjects"],
3134
{
32-
headers: {
33-
Authorization: `Bearer ${token}`,
34-
},
35+
tags: [projectCacheTag(token)],
36+
revalidate: 3600, // 1 hour
3537
},
3638
);
37-
if (teamsRes.ok) {
38-
return (await teamsRes.json())?.result as Project[];
39+
40+
return getCachedProjects(token, teamSlug);
41+
}
42+
43+
export async function getProjectsForAuthToken(
44+
authToken: string,
45+
teamSlug: string,
46+
) {
47+
console.log("FETCHING PROJECTS ------------------------");
48+
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`, {
49+
headers: {
50+
Authorization: `Bearer ${authToken}`,
51+
},
52+
});
53+
if (res.ok) {
54+
return (await res.json())?.result as Project[];
3955
}
4056
return [];
4157
}
4258

4359
export async function getProject(teamSlug: string, projectSlug: string) {
44-
const token = await getAuthToken();
60+
const authToken = await getAuthToken();
4561

46-
if (!token) {
62+
if (!authToken) {
4763
return null;
4864
}
4965

50-
const teamsRes = await fetch(
66+
const getCachedProject = unstable_cache(
67+
getProjectForAuthToken,
68+
["getProject"],
69+
{
70+
tags: [projectCacheTag(authToken)],
71+
revalidate: 3600, // 1 hour
72+
},
73+
);
74+
75+
return getCachedProject(authToken, teamSlug, projectSlug);
76+
}
77+
78+
async function getProjectForAuthToken(
79+
authToken: string,
80+
teamSlug: string,
81+
projectSlug: string,
82+
) {
83+
console.log(
84+
"FETCHING PROJECT ------------------------",
85+
teamSlug,
86+
projectSlug,
87+
);
88+
const res = await fetch(
5189
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects/${projectSlug}`,
5290
{
5391
headers: {
54-
Authorization: `Bearer ${token}`,
92+
Authorization: `Bearer ${authToken}`,
5593
},
5694
},
5795
);
58-
if (teamsRes.ok) {
59-
return (await teamsRes.json())?.result as Project;
96+
97+
if (res.ok) {
98+
return (await res.json())?.result as Project;
6099
}
61100
return null;
62101
}

apps/dashboard/src/@/api/team.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import "server-only";
22
import { API_SERVER_URL } from "@/constants/env";
3+
import { unstable_cache } from "next/cache";
34
import { getAuthToken } from "../../app/api/lib/getAuthToken";
5+
import { teamCacheTag } from "../constants/cacheTags";
46

57
type EnabledTeamScope =
68
| "pay"
@@ -29,16 +31,31 @@ export type Team = {
2931
enabledScopes: EnabledTeamScope[];
3032
};
3133

32-
export async function getTeamBySlug(slug: string) {
33-
const token = await getAuthToken();
34+
export async function getTeamBySlug(teamSlug: string) {
35+
const authToken = await getAuthToken();
3436

35-
if (!token) {
37+
if (!authToken) {
3638
return null;
3739
}
3840

39-
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
41+
const getCachedTeam = unstable_cache(
42+
getTeamBySlugAndAuthToken,
43+
["getTeamBySlug"],
44+
{
45+
tags: [teamCacheTag(authToken)],
46+
revalidate: 3600, // 1 hour
47+
},
48+
);
49+
50+
return getCachedTeam(teamSlug, authToken);
51+
}
52+
53+
async function getTeamBySlugAndAuthToken(teamSlug: string, authToken: string) {
54+
console.log("FETCHING TEAM ------------------------", teamSlug);
55+
56+
const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}`, {
4057
headers: {
41-
Authorization: `Bearer ${token}`,
58+
Authorization: `Bearer ${authToken}`,
4259
},
4360
});
4461
if (teamRes.ok) {
@@ -48,14 +65,26 @@ export async function getTeamBySlug(slug: string) {
4865
}
4966

5067
export async function getTeams() {
51-
const token = await getAuthToken();
52-
if (!token) {
68+
const authToken = await getAuthToken();
69+
70+
if (!authToken) {
5371
return null;
5472
}
5573

74+
const getCachedTeams = unstable_cache(getTeamsForAuthToken, ["getTeams"], {
75+
tags: [teamCacheTag(authToken)],
76+
revalidate: 3600, // 1 hour
77+
});
78+
79+
return getCachedTeams(authToken);
80+
}
81+
82+
async function getTeamsForAuthToken(authToken: string) {
83+
console.log("FETCHING ALL TEAMs ------------------------");
84+
5685
const teamsRes = await fetch(`${API_SERVER_URL}/v1/teams`, {
5786
headers: {
58-
Authorization: `Bearer ${token}`,
87+
Authorization: `Bearer ${authToken}`,
5988
},
6089
});
6190
if (teamsRes.ok) {
@@ -70,24 +99,39 @@ type TeamNebulaWaitList = {
7099
};
71100

72101
export async function getTeamNebulaWaitList(teamSlug: string) {
73-
const token = await getAuthToken();
102+
const authToken = await getAuthToken();
74103

75-
if (!token) {
104+
if (!authToken) {
76105
return null;
77106
}
78107

108+
const getCachedNebulaWaitlist = unstable_cache(
109+
getTeamNebulaWaitListForAuthToken,
110+
["getTeamNebulaWaitList"],
111+
{
112+
tags: [teamCacheTag(authToken)],
113+
revalidate: 3600, // 1 hour
114+
},
115+
);
116+
117+
return getCachedNebulaWaitlist(teamSlug, authToken);
118+
}
119+
120+
async function getTeamNebulaWaitListForAuthToken(
121+
teamSlug: string,
122+
authToken: string,
123+
) {
79124
const res = await fetch(
80125
`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist?scope=nebula`,
81126
{
82127
headers: {
83-
Authorization: `Bearer ${token}`,
128+
Authorization: `Bearer ${authToken}`,
84129
},
85130
},
86131
);
87132

88133
if (res.ok) {
89134
return (await res.json()).result as TeamNebulaWaitList;
90135
}
91-
92136
return null;
93137
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { keccak256 } from "thirdweb";
2+
3+
// TODO: technically should be using account.id instead of authToken
4+
// but that's gonna require a lot of code changes, so authToken it is for now
5+
6+
export function teamCacheTag(authToken: string) {
7+
return `team-${shortenAuthToken(authToken)}`;
8+
}
9+
10+
export function projectCacheTag(authToken: string) {
11+
return `project-${shortenAuthToken(authToken)}`;
12+
}
13+
14+
function shortenAuthToken(authToken: string) {
15+
// authToken is too long for the next.js cache tag, we have to shorten it
16+
// shorten auth token by creating a hash of it
17+
18+
const authTokenHash = keccak256(new TextEncoder().encode(authToken));
19+
return authTokenHash;
20+
}

0 commit comments

Comments
 (0)