Skip to content

Commit cec80ac

Browse files
committed
[Experiment] unstable_cache: Teams and Projects
1 parent 633f0ca commit cec80ac

File tree

19 files changed

+321
-188
lines changed

19 files changed

+321
-188
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"use server";
22

3+
import { revalidateTag } from "next/cache";
34
import { getAuthToken } from "../../app/api/lib/getAuthToken";
5+
import { accountCacheTag } from "../constants/cacheTags";
46
import { API_SERVER_URL } from "../constants/env";
57

68
export async function confirmEmailWithOTP(otp: string) {
@@ -36,4 +38,6 @@ export async function confirmEmailWithOTP(otp: string) {
3638
errorMessage: "Failed to confirm email",
3739
};
3840
}
41+
42+
revalidateTag(accountCacheTag(token));
3943
}

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

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"use server";
2-
2+
import { revalidateTag } from "next/cache";
33
import { getAuthToken } from "../../app/api/lib/getAuthToken";
4+
import {
5+
accountCacheTag,
6+
projectsCacheTag,
7+
teamsCacheTag,
8+
} from "../constants/cacheTags";
49
import { API_SERVER_URL } from "../constants/env";
510

611
type ProxyActionParams = {
@@ -9,6 +14,13 @@ type ProxyActionParams = {
914
method: "GET" | "POST" | "PUT" | "DELETE";
1015
body?: string;
1116
headers?: Record<string, string>;
17+
revalidateCacheTags?: RevalidateCacheTagOptions;
18+
};
19+
20+
type RevalidateCacheTagOptions = {
21+
teamTag?: true;
22+
projectTag?: true;
23+
accountTag?: true;
1224
};
1325

1426
type ProxyActionResult<T extends object> =
@@ -28,6 +40,15 @@ async function proxy<T extends object>(
2840
params: ProxyActionParams,
2941
): Promise<ProxyActionResult<T>> {
3042
const authToken = await getAuthToken();
43+
const { revalidateCacheTags: revalidateCacheTag } = params;
44+
45+
if (!authToken) {
46+
return {
47+
status: 401,
48+
ok: false,
49+
error: "Unauthorized",
50+
};
51+
}
3152

3253
// build URL
3354
const url = new URL(baseUrl);
@@ -64,6 +85,20 @@ async function proxy<T extends object>(
6485
}
6586
}
6687

88+
if (revalidateCacheTag) {
89+
if (revalidateCacheTag.teamTag) {
90+
revalidateTag(teamsCacheTag(authToken));
91+
}
92+
93+
if (revalidateCacheTag.projectTag) {
94+
revalidateTag(projectsCacheTag(authToken));
95+
}
96+
97+
if (revalidateCacheTag.accountTag) {
98+
revalidateTag(accountCacheTag(authToken));
99+
}
100+
}
101+
67102
return {
68103
status: res.status,
69104
ok: true,

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
"use server";
2+
import { revalidateTag } from "next/cache";
23
import { getAuthToken } from "../../app/api/lib/getAuthToken";
4+
import { accountCacheTag } from "../constants/cacheTags";
35
import { API_SERVER_URL } from "../constants/env";
46

57
export async function updateAccount(values: {
68
name?: string;
79
email?: string;
810
image?: string | null;
911
}) {
10-
const token = await getAuthToken();
12+
const authToken = await getAuthToken();
1113

12-
if (!token) {
14+
if (!authToken) {
1315
throw new Error("No Auth token");
1416
}
1517

1618
const res = await fetch(`${API_SERVER_URL}/v1/account`, {
1719
method: "PUT",
1820
headers: {
1921
"Content-Type": "application/json",
20-
Authorization: `Bearer ${token}`,
22+
Authorization: `Bearer ${authToken}`,
2123
},
2224
body: JSON.stringify(values),
2325
});
@@ -35,4 +37,6 @@ export async function updateAccount(values: {
3537
errorMessage: "Failed To Update Account",
3638
};
3739
}
40+
41+
revalidateTag(accountCacheTag(authToken));
3842
}

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
44
import { cookies } from "next/headers";
55
import { getAddress } from "thirdweb";
6+
import { getCachedRawAccountForAuthToken } from "../../app/account/settings/getAccount";
67
import { COOKIE_PREFIX_TOKEN } from "../constants/cookie";
7-
import { API_SERVER_URL } from "../constants/env";
88

99
/**
1010
* Check that the connected wallet is valid for the current active account
@@ -30,19 +30,12 @@ export async function isWalletValidForActiveAccount(params: {
3030
}
3131

3232
// authToken should be valid
33-
const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, {
34-
method: "GET",
35-
headers: {
36-
Authorization: `Bearer ${authTokenForAddress}`,
37-
},
38-
});
33+
const account = await getCachedRawAccountForAuthToken(authTokenForAddress);
3934

40-
if (accountRes.status !== 200) {
35+
if (!account) {
4136
return false;
4237
}
4338

44-
const account = (await accountRes.json()).data as Account;
45-
4639
// the current account should match the account fetched for the authToken
4740
return account.id === params.account.id;
4841
}

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 { projectsCacheTag } 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: [projectsCacheTag(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: [projectsCacheTag(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 { teamsCacheTag } 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+
getTeamBySlugForAuthToken,
43+
["getTeamBySlug"],
44+
{
45+
tags: [teamsCacheTag(authToken)],
46+
revalidate: 3600, // 1 hour
47+
},
48+
);
49+
50+
return getCachedTeam(teamSlug, authToken);
51+
}
52+
53+
async function getTeamBySlugForAuthToken(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: [teamsCacheTag(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: [teamsCacheTag(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+
export function teamsCacheTag(authToken: string) {
4+
return `${shortenAuthToken(authToken)}/teams`;
5+
}
6+
7+
export function projectsCacheTag(authToken: string) {
8+
return `${shortenAuthToken(authToken)}/projects`;
9+
}
10+
11+
export function accountCacheTag(authToken: string) {
12+
return `${shortenAuthToken(authToken)}/account`;
13+
}
14+
15+
function shortenAuthToken(authToken: string) {
16+
// authToken is too long for the next.js cache tag, we have to shorten it
17+
// shorten auth token by creating a hash of it
18+
const authTokenHash = keccak256(new TextEncoder().encode(authToken));
19+
return authTokenHash;
20+
}

0 commit comments

Comments
 (0)