Skip to content
Closed
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
4 changes: 4 additions & 0 deletions apps/dashboard/src/@/actions/confirmEmail.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use server";

import { revalidateTag } from "next/cache";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { accountCacheTag } from "../constants/cacheTags";
import { API_SERVER_URL } from "../constants/env";

export async function confirmEmailWithOTP(otp: string) {
Expand Down Expand Up @@ -36,4 +38,6 @@ export async function confirmEmailWithOTP(otp: string) {
errorMessage: "Failed to confirm email",
};
}

revalidateTag(accountCacheTag(token));
}
37 changes: 36 additions & 1 deletion apps/dashboard/src/@/actions/proxies.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"use server";

import { revalidateTag } from "next/cache";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import {
accountCacheTag,
projectsCacheTag,
teamsCacheTag,
} from "../constants/cacheTags";
import { API_SERVER_URL } from "../constants/env";

type ProxyActionParams = {
Expand All @@ -9,6 +14,13 @@ type ProxyActionParams = {
method: "GET" | "POST" | "PUT" | "DELETE";
body?: string;
headers?: Record<string, string>;
revalidateCacheTags?: RevalidateCacheTagOptions;
};

type RevalidateCacheTagOptions = {
teamTag?: true;
projectTag?: true;
accountTag?: true;
};

type ProxyActionResult<T extends object> =
Expand All @@ -28,6 +40,15 @@ async function proxy<T extends object>(
params: ProxyActionParams,
): Promise<ProxyActionResult<T>> {
const authToken = await getAuthToken();
const { revalidateCacheTags: revalidateCacheTag } = params;

if (!authToken) {
return {
status: 401,
ok: false,
error: "Unauthorized",
};
}

// build URL
const url = new URL(baseUrl);
Expand Down Expand Up @@ -64,6 +85,20 @@ async function proxy<T extends object>(
}
}

if (revalidateCacheTag) {
if (revalidateCacheTag.teamTag) {
revalidateTag(teamsCacheTag(authToken));
}

if (revalidateCacheTag.projectTag) {
revalidateTag(projectsCacheTag(authToken));
}

if (revalidateCacheTag.accountTag) {
revalidateTag(accountCacheTag(authToken));
}
}

return {
status: res.status,
ok: true,
Expand Down
10 changes: 7 additions & 3 deletions apps/dashboard/src/@/actions/updateAccount.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
"use server";
import { revalidateTag } from "next/cache";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { accountCacheTag } from "../constants/cacheTags";
import { API_SERVER_URL } from "../constants/env";

export async function updateAccount(values: {
name?: string;
email?: string;
image?: string | null;
}) {
const token = await getAuthToken();
const authToken = await getAuthToken();

if (!token) {
if (!authToken) {
throw new Error("No Auth token");
}

const res = await fetch(`${API_SERVER_URL}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(values),
});
Expand All @@ -35,4 +37,6 @@ export async function updateAccount(values: {
errorMessage: "Failed To Update Account",
};
}

revalidateTag(accountCacheTag(authToken));
}
13 changes: 3 additions & 10 deletions apps/dashboard/src/@/actions/validLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { cookies } from "next/headers";
import { getAddress } from "thirdweb";
import { getCachedRawAccountForAuthToken } from "../../app/account/settings/getAccount";
import { COOKIE_PREFIX_TOKEN } from "../constants/cookie";
import { API_SERVER_URL } from "../constants/env";

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

// authToken should be valid
const accountRes = await fetch(`${API_SERVER_URL}/v1/account/me`, {
method: "GET",
headers: {
Authorization: `Bearer ${authTokenForAddress}`,
},
});
const account = await getCachedRawAccountForAuthToken(authTokenForAddress);

if (accountRes.status !== 200) {
if (!account) {
return false;
}

const account = (await accountRes.json()).data as Account;

// the current account should match the account fetched for the authToken
return account.id === params.account.id;
}
65 changes: 52 additions & 13 deletions apps/dashboard/src/@/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { unstable_cache } from "next/cache";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { projectsCacheTag } from "../constants/cacheTags";

export type Project = {
id: string;
Expand All @@ -26,37 +28,74 @@ export async function getProjects(teamSlug: string) {
return [];
}

const teamsRes = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`,
const getCachedProjects = unstable_cache(
getProjectsForAuthToken,
["getProjects"],
{
headers: {
Authorization: `Bearer ${token}`,
},
tags: [projectsCacheTag(token)],
revalidate: 3600, // 1 hour
},
);
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Project[];

return getCachedProjects(token, teamSlug);
}

export async function getProjectsForAuthToken(
authToken: string,
teamSlug: string,
) {
console.log("FETCHING PROJECTS ------------------------");
const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/projects`, {
headers: {
Authorization: `Bearer ${authToken}`,
},
});
if (res.ok) {
return (await res.json())?.result as Project[];
}
return [];
}

export async function getProject(teamSlug: string, projectSlug: string) {
const token = await getAuthToken();
const authToken = await getAuthToken();

if (!token) {
if (!authToken) {
return null;
}

const teamsRes = await fetch(
const getCachedProject = unstable_cache(
getProjectForAuthToken,
["getProject"],
{
tags: [projectsCacheTag(authToken)],
revalidate: 3600, // 1 hour
},
);

return getCachedProject(authToken, teamSlug, projectSlug);
}

async function getProjectForAuthToken(
authToken: string,
teamSlug: string,
projectSlug: string,
) {
console.log(
"FETCHING PROJECT ------------------------",
teamSlug,
projectSlug,
);
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/projects/${projectSlug}`,
{
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${authToken}`,
},
},
);
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Project;

if (res.ok) {
return (await res.json())?.result as Project;
}
return null;
}
68 changes: 56 additions & 12 deletions apps/dashboard/src/@/api/team.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "server-only";
import { API_SERVER_URL } from "@/constants/env";
import { unstable_cache } from "next/cache";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { teamsCacheTag } from "../constants/cacheTags";

type EnabledTeamScope =
| "pay"
Expand Down Expand Up @@ -29,16 +31,31 @@ export type Team = {
enabledScopes: EnabledTeamScope[];
};

export async function getTeamBySlug(slug: string) {
const token = await getAuthToken();
export async function getTeamBySlug(teamSlug: string) {
const authToken = await getAuthToken();

if (!token) {
if (!authToken) {
return null;
}

const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${slug}`, {
const getCachedTeam = unstable_cache(
getTeamBySlugForAuthToken,
["getTeamBySlug"],
{
tags: [teamsCacheTag(authToken)],
revalidate: 3600, // 1 hour
},
);

return getCachedTeam(teamSlug, authToken);
}

async function getTeamBySlugForAuthToken(teamSlug: string, authToken: string) {
console.log("FETCHING TEAM ------------------------", teamSlug);

const teamRes = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}`, {
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${authToken}`,
},
});
if (teamRes.ok) {
Expand All @@ -48,14 +65,26 @@ export async function getTeamBySlug(slug: string) {
}

export async function getTeams() {
const token = await getAuthToken();
if (!token) {
const authToken = await getAuthToken();

if (!authToken) {
return null;
}

const getCachedTeams = unstable_cache(getTeamsForAuthToken, ["getTeams"], {
tags: [teamsCacheTag(authToken)],
revalidate: 3600, // 1 hour
});

return getCachedTeams(authToken);
}

async function getTeamsForAuthToken(authToken: string) {
console.log("FETCHING ALL TEAMs ------------------------");

const teamsRes = await fetch(`${API_SERVER_URL}/v1/teams`, {
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${authToken}`,
},
});
if (teamsRes.ok) {
Expand All @@ -70,24 +99,39 @@ type TeamNebulaWaitList = {
};

export async function getTeamNebulaWaitList(teamSlug: string) {
const token = await getAuthToken();
const authToken = await getAuthToken();

if (!token) {
if (!authToken) {
return null;
}

const getCachedNebulaWaitlist = unstable_cache(
getTeamNebulaWaitListForAuthToken,
["getTeamNebulaWaitList"],
{
tags: [teamsCacheTag(authToken)],
revalidate: 3600, // 1 hour
},
);

return getCachedNebulaWaitlist(teamSlug, authToken);
}

async function getTeamNebulaWaitListForAuthToken(
teamSlug: string,
authToken: string,
) {
const res = await fetch(
`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist?scope=nebula`,
{
headers: {
Authorization: `Bearer ${token}`,
Authorization: `Bearer ${authToken}`,
},
},
);

if (res.ok) {
return (await res.json()).result as TeamNebulaWaitList;
}

return null;
}
20 changes: 20 additions & 0 deletions apps/dashboard/src/@/constants/cacheTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { keccak256 } from "thirdweb";

export function teamsCacheTag(authToken: string) {
return `${shortenAuthToken(authToken)}/teams`;
}

export function projectsCacheTag(authToken: string) {
return `${shortenAuthToken(authToken)}/projects`;
}

export function accountCacheTag(authToken: string) {
return `${shortenAuthToken(authToken)}/account`;
}

function shortenAuthToken(authToken: string) {
// authToken is too long for the next.js cache tag, we have to shorten it
// shorten auth token by creating a hash of it
const authTokenHash = keccak256(new TextEncoder().encode(authToken));
return authTokenHash;
}
Loading
Loading