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

feat: use unstable_cache everywhere #121

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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: 1 addition & 1 deletion app/(dashboard)/manage-repos/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function SelectRepoPage() {
{repos.length > 0 ? (
<>
{repos.map((repo) => (
<RepoSelector key={repo.id} repo={repo} />
<RepoSelector key={repo.id} repo={repo} userId={user.id} />
))}
</>
) : (
Expand Down
9 changes: 8 additions & 1 deletion app/(dashboard)/manage-repos/repoSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import { TRepository } from "@/types/repository";
import { useCallback, useEffect, useState } from "react";

import { activateRepoAction, deactivateRepoAction, fetchRepoDetailsAction } from "./actions";
import { cacheTags, revalidate } from "@/lib/cache";
import { enrolledRepositoriesCache } from "@/lib/enrollment/cache";
import { repositoryCache } from "@/lib/repository/cache";

interface RepoSelectorProps {
repo: TRepository;
// required for cache revalidation
userId: string
}

export const RepoSelector: React.FC<RepoSelectorProps> = ({ repo: initialRepo }) => {
export const RepoSelector: React.FC<RepoSelectorProps> = ({ repo: initialRepo, userId }) => {
const { toast } = useToast();
const [repo, setRepo] = useState<TRepository>(initialRepo);
const [isLoading, setIsLoading] = useState(false);
Expand Down Expand Up @@ -57,6 +62,8 @@ export const RepoSelector: React.FC<RepoSelectorProps> = ({ repo: initialRepo })
});
}
await fetchRepoDetails();
enrolledRepositoriesCache.revalidate({ userId })
repositoryCache.revalidate({})
} catch (error) {
console.error("Error changing repository activation status", error);
toast({
Expand Down
12 changes: 6 additions & 6 deletions app/[githubLogin]/components/profile-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import ProfileInfoBar from "./profile-info";

export default async function ProfilePage({ githubLogin }: { githubLogin: string }) {
// Get & enrich the player data
const enrichedUserData = await getEnrichedGithubUserData(githubLogin);

const enrichedUserData = await getEnrichedGithubUserData(githubLogin)
let pointsAndRanks: Array<{
id: string;
repositoryName: string;
Expand All @@ -23,16 +22,17 @@ export default async function ProfilePage({ githubLogin }: { githubLogin: string

if (enrichedUserData.enrolledRepositories && enrichedUserData.playerData?.id) {
try {

const result = await getPointsAndRankPerRepository(
enrichedUserData.enrolledRepositories,
enrichedUserData.playerData.id
);
)

pointsAndRanks = result.map((item) => ({
...item,
repositoryLogo: item.repositoryLogo || undefined,
}));
} catch (error) {}
} catch (error) { }
}

let totalPoints = 0;
Expand All @@ -45,13 +45,13 @@ export default async function ProfilePage({ githubLogin }: { githubLogin: string
chanceOfWinning = result.likelihoodOfWinning;
}

const ossGgRepositories = await getAllRepositories();
const ossGgRepositories = await getAllRepositories()

let pullRequests = [] as TPullRequest[];

if (enrichedUserData.status.githubUserFound) {
const ossGgRepositoriesIds = ossGgRepositories.map((repo) => `${repo.owner}/${repo.name}`);
pullRequests = await getPullRequestsByGithubLogin(ossGgRepositoriesIds, githubLogin);
pullRequests = await getPullRequestsByGithubLogin(ossGgRepositoriesIds, githubLogin)
}

return (
Expand Down
7 changes: 7 additions & 0 deletions lib/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { unstable_cache as cache } from "next/cache"


// utility function to cache a function with tags and optional revalidation
export async function withCache<T>(fn: () => Promise<T>, tags: string[], opts?: { revalidate?: number }): Promise<T> {
return cache(fn, tags, { tags, revalidate: opts?.revalidate })()
}
18 changes: 18 additions & 0 deletions lib/enrollment/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { revalidateTag } from "next/cache";

type RevalidateProps = {
userId?: string;
}

export const enrolledRepositoriesCache = {
tags: {
byUserId(userId: string) {
return [`enrolled-repositories-${userId}`];
},
},
revalidate({ userId }: RevalidateProps): void {
if (userId) {
revalidateTag(this.tags.byUserId(userId));
}
},
};
20 changes: 16 additions & 4 deletions lib/enrollment/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { TRepository } from "@/types/repository";
import { Prisma } from "@prisma/client";

import { validateInputs } from "../utils/validate";

import { revalidate, cacheTags, withCache } from "@/lib/cache"
import { enrolledRepositoriesCache } from "./cache";
/**
* Enrolls a user in all repositories.
* @param userId - The ID of the user to enroll.
Expand All @@ -32,6 +33,11 @@ export const enrollUserInAllRepositories = async (userId: string) => {
data: enrollments,
skipDuplicates: true,
});


// we need a username
// await revalidate(cacheTags.enrichedProfile(userId))

} catch (error) {
throw error;
}
Expand Down Expand Up @@ -62,7 +68,6 @@ export const createEnrollment = async (enrollmentData: TEnrollmentInput): Promis
const enrollment = await db.enrollment.create({
data: enrollmentData,
});

return enrollment;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
Expand All @@ -89,6 +94,8 @@ export const deleteEnrollment = async (userId: string, repositoryId: string): Pr
},
},
});
await revalidate(cacheTags.enrichedProfile(userId))

} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
throw new DatabaseError(error.message);
Expand Down Expand Up @@ -121,7 +128,7 @@ export const hasEnrollmentForRepository = async (userId: string, repositoryId: s
* a repository the user is enrolled in. The array is empty if the user has no enrollments.
*/

export const getEnrolledRepositories = async (userId: string): Promise<TRepository[]> => {
export const getEnrolledRepositories = async (userId: string): Promise<TRepository[]> => withCache(async () => {
const enrolledRepositories = await db.repository.findMany({
where: {
enrollments: {
Expand Down Expand Up @@ -149,4 +156,9 @@ export const getEnrolledRepositories = async (userId: string): Promise<TReposito
});

return enrolledRepositories;
};
},
enrolledRepositoriesCache.tags.byUserId(userId),
{
revalidate: 24 * 60 * 60,
}
)
7 changes: 1 addition & 6 deletions lib/github/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type PullRequestStatus = "open" | "merged" | "closed" | undefined;

const octokit = new Octokit({ auth: GITHUB_APP_ACCESS_TOKEN });

const fetchPullRequestsByGithubLogin = async (
export const getPullRequestsByGithubLogin = async (
playerRepositoryIds: string[],
githubLogin: string,
status?: PullRequestStatus
Expand Down Expand Up @@ -79,11 +79,6 @@ const fetchPullRequestsByGithubLogin = async (
return pullRequests;
};

export const getPullRequestsByGithubLogin = unstable_cache(
fetchPullRequestsByGithubLogin,
["fetchPullRequestsByGithubLogin"],
{ revalidate: 60 }
);

const fetchAllOssGgIssuesOfRepos = async (
repos: { id: number; fullName: string }[]
Expand Down
1 change: 0 additions & 1 deletion lib/github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import crypto from "node:crypto";
import { GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, OSS_GG_LABEL } from "../constants";
import { assignUserPoints } from "../points/service";
import { createUser, getUserByGithubId } from "../user/service";

export const getOctokitInstance = (installationId: number) => {
if (!installationId) {
throw new Error("No installation id provided");
Expand Down
6 changes: 3 additions & 3 deletions lib/githubUser/cache.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { revalidateTag } from "next/cache";

interface RevalidateProps {
type RevalidateProps = {
githubLogin?: string;
}

export const githubUserCache = {
tag: {
tags: {
byGithubLogin(githubLogin: string) {
return `github-users-${githubLogin}`;
return [`github-users-${githubLogin}`];
},
},
revalidate({ githubLogin }: RevalidateProps): void {
Expand Down
13 changes: 6 additions & 7 deletions lib/githubUser/service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { GITHUB_APP_ACCESS_TOKEN } from "@/lib/constants";
import { TGithubUserData, ZGithubUserData } from "@/types/githubUser";
import { type TGithubUserData, ZGithubUserData } from "@/types/githubUser";
// Adjust the import path as needed
import { Octokit } from "@octokit/rest";
import { unstable_cache } from "next/cache";

import { githubUserCache } from "./cache";
import { withCache } from "@/lib/cache";

export const getGithubUserByLogin = (githubLogin: string): Promise<TGithubUserData | false> =>
unstable_cache(
withCache(
async () => {
try {
const octokit = new Octokit({ auth: `token ${GITHUB_APP_ACCESS_TOKEN}` });
Expand All @@ -33,9 +33,8 @@ export const getGithubUserByLogin = (githubLogin: string): Promise<TGithubUserDa
return false;
}
},
[`getGithubUserByLogin-${githubLogin}`],
githubUserCache.tags.byGithubLogin(githubLogin),
{
tags: [githubUserCache.tag.byGithubLogin(githubLogin)],
revalidate: 60 * 60 * 24 * 7, // 1 week
revalidate: 7 * 24 * 60 * 60, // 7 days
}
)();
)
46 changes: 39 additions & 7 deletions lib/points/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,49 @@ import { revalidateTag } from "next/cache";

interface RevalidateProps {
repositoryId?: string;
userId?: string;

}

export const pointsCache = {
tag: {
byRepositoryId(repositoryId: string) {
return `repositories-${repositoryId}-points`;
},
tags: {
byRepositoryId: (repositoryId: string, page?: number) => [
`points-repositories-${repositoryId}`,
`points-repositories-${repositoryId}-page-${page}`
],
byPointsForPlayerInRepoByRepositoryId: (userId: string, repositoryId: string) => [
`points-user-${userId}`,
`points-user-${userId}-repository-${repositoryId}`
],
getPointsAndRankPerRepository: (userId: string, repositoryId: string) => [
`points-user-${userId}`,
`points-user-${userId}-repository-${repositoryId}`
],
rankByPlayerId: (userId: string) => [
`points-user-${userId}-rank`
],

},
revalidate({ repositoryId }: RevalidateProps): void {
revalidate({ repositoryId, userId }: RevalidateProps): void {
const tags = new Set<string>()
if (repositoryId) {
revalidateTag(this.tag.byRepositoryId(repositoryId));
for (const tag of this.tags.byRepositoryId(repositoryId)) {
tags.add(tag);
}
}
if (userId) {
for (const tag of this.tags.rankByPlayerId(userId)) {
tags.add(tag);
}
}
if (repositoryId && userId) {
for (const tag of this.tags.byPlayerAndRepositoryId(userId, repositoryId)) {
tags.add(tag);
}
for (const tag of this.tags.getPointsAndRankPerRepository(userId, repositoryId)) {
tags.add(tag);
}
}
tags.forEach((tag) => revalidateTag(tag))
},
};
}
Loading
Loading