diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 49cecbd..41fd067 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -29,6 +29,7 @@ model User {
spotifyToken SpotifyToken?
RecentActivity RecentActivity[]
IntegrationChessCom IntegrationChessCom?
+ IntegrationLeetCode IntegrationLeetCode?
CryptoWallets CryptoWallets[]
}
@@ -108,6 +109,16 @@ model IntegrationChessCom {
updatedAt DateTime @updatedAt
}
+model IntegrationLeetCode {
+ id Int @id @default(autoincrement())
+ usedBy User @relation(fields: [userId], references: [githubId])
+ userId Int @unique
+ username String
+ visible Boolean @default(true)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+}
+
model CryptoWallets {
id Int @id @default(autoincrement())
usedBy User @relation(fields: [userId], references: [githubId])
diff --git a/src/lib/components/MyProfile/LeetCodeForm.svelte b/src/lib/components/MyProfile/LeetCodeForm.svelte
new file mode 100644
index 0000000..7750a74
--- /dev/null
+++ b/src/lib/components/MyProfile/LeetCodeForm.svelte
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/lib/components/MyProfile/LeetCodeStats.svelte b/src/lib/components/MyProfile/LeetCodeStats.svelte
new file mode 100644
index 0000000..e17fb16
--- /dev/null
+++ b/src/lib/components/MyProfile/LeetCodeStats.svelte
@@ -0,0 +1,120 @@
+
+
+
+
+ LeetCode
+ {leetCodeUsername}'s LeetCode stats
+
+
+
+ {#if loading}
+ Loading...
+ {:else if data}
+
+ Problem Solving Summary
+
+
+
+ Difficulty
+ Count
+
+
+
+
+ Easy
+ {easyCount}
+
+
+ Medium
+ {mediumCount}
+
+
+ Hard
+ {hardCount}
+
+
+
+
+
+ {#if data.userCalendar.dccBadges.length > 0}
+ Badges
+
+ {#each data.userCalendar.dccBadges as badge}
+
+
+
+ {badge.badge.name}
+
+
+ {/each}
+
+ {/if}
+
+
+ Metrics
+
+
+
+
+ Longest Streak:
+ {data.userCalendar.streak} days
+
+
+
+ Active Days:
+ {data.userCalendar.totalActiveDays}
+
+
+
+ Ranking:
+ {data.profile.ranking}
+
+
+
+ {:else}
+ No data available.
+ {/if}
+
+
+
+
diff --git a/src/lib/components/PublicProfile/PublicProfile.svelte b/src/lib/components/PublicProfile/PublicProfile.svelte
index 350e21e..9e53191 100644
--- a/src/lib/components/PublicProfile/PublicProfile.svelte
+++ b/src/lib/components/PublicProfile/PublicProfile.svelte
@@ -8,10 +8,10 @@
import ProfileHero from '$lib/components/PublicProfile/ProfileHero.svelte';
import { Separator } from '$lib/components//ui/separator';
import ChessComStats from '$lib/components/MyProfile/ChessComStats.svelte';
+ import LeetCodeStats from '$lib/components/MyProfile/LeetCodeStats.svelte';
// Accept userData as a prop
export let userData: PublicProfile;
-
//Accept githubData as a prop
export let githubData: GithubData | null;
@@ -37,6 +37,11 @@
{#if userData.chessComUsername != null}
{/if}
+
+
+ {#if userData.leetCode != null}
+
+ {/if}
diff --git a/src/lib/schemas/integration-leetcode.ts b/src/lib/schemas/integration-leetcode.ts
new file mode 100644
index 0000000..0b6ea28
--- /dev/null
+++ b/src/lib/schemas/integration-leetcode.ts
@@ -0,0 +1,11 @@
+import { z } from 'zod';
+
+export const leetCodeSchema = z.object({
+ username: z.string().min(3).max(20)
+ });
+
+
+export type LeetCodeSchema = typeof leetCodeSchema;
+
+
+
diff --git a/src/lib/types/LeetCodeData.ts b/src/lib/types/LeetCodeData.ts
new file mode 100644
index 0000000..55fb79f
--- /dev/null
+++ b/src/lib/types/LeetCodeData.ts
@@ -0,0 +1,30 @@
+export type LeetCodeStats = {
+ username: string;
+ userCalendar: {
+ activeYears: number[];
+ streak: number;
+ totalActiveDays: number;
+ dccBadges: {
+ timestamp: string;
+ badge: {
+ name: string;
+ icon: string;
+ };
+ }[];
+ };
+ profile: {
+ ranking: number;
+ };
+ submitStatsGlobal:{
+ acSubmissionNum: {
+ difficulty: string;
+ count: number;
+ }[];
+ }
+};
+
+export type TagProblem = {
+ tagName: string;
+ tagSlug: string;
+ problemsSolved: number;
+};
diff --git a/src/lib/types/PublicProfile.ts b/src/lib/types/PublicProfile.ts
index cfe14c5..6e79b0f 100644
--- a/src/lib/types/PublicProfile.ts
+++ b/src/lib/types/PublicProfile.ts
@@ -1,4 +1,4 @@
-import type { CryptoWallets, PersonalInformation, Social } from '@prisma/client';
+import type { CryptoWallets, PersonalInformation, Social, IntegrationLeetCode} from '@prisma/client';
export interface PublicProfile {
links: Array<{ title: string; url: string }>;
@@ -10,4 +10,5 @@ export interface PublicProfile {
personalInformation: PersonalInformation | null;
chessComUsername: string | null;
crypto: CryptoWallets[];
+ leetCode: IntegrationLeetCode | null;
}
diff --git a/src/lib/utils/createRecentActivity.ts b/src/lib/utils/createRecentActivity.ts
index 83e8a5c..5b7d320 100644
--- a/src/lib/utils/createRecentActivity.ts
+++ b/src/lib/utils/createRecentActivity.ts
@@ -17,7 +17,9 @@ export const createRecentActivity = async (
| 'CHESS_COM_DELETED'
| 'PERSONAL_INFORMATION_UPDATED'
| 'CRYPTO_CREATED'
- | 'CRYPTO_DELETED',
+ | 'CRYPTO_DELETED'
+ | 'LEETCODE_LINKED'
+ | 'LEETCODE_UNLINKED',
activityDescription: string,
userId: number
): Promise
=> {
diff --git a/src/routes/[username]/+page.server.ts b/src/routes/[username]/+page.server.ts
index bd52064..e1da7ed 100644
--- a/src/routes/[username]/+page.server.ts
+++ b/src/routes/[username]/+page.server.ts
@@ -60,6 +60,10 @@ export const load: PageServerLoad = async ({ params }) => {
const crypto = await prisma.cryptoWallets.findMany({
where: { userId: user.githubId }
});
+ const leetCode = await prisma.integrationLeetCode.findUnique({
+ where: { userId: user.githubId }
+ });
+
const userData: PublicProfile = {
links,
@@ -70,7 +74,10 @@ export const load: PageServerLoad = async ({ params }) => {
username: username,
isOpenToCollaborating: isOpenToCollaborating?.openToCollaborating,
hobbies,
- crypto
+ crypto,
+ // TODO add leetCode to the userData
+ leetCode
+
};
return {
diff --git a/src/routes/api/leetcode/+server.ts b/src/routes/api/leetcode/+server.ts
new file mode 100644
index 0000000..83da272
--- /dev/null
+++ b/src/routes/api/leetcode/+server.ts
@@ -0,0 +1,56 @@
+import type { RequestHandler } from '@sveltejs/kit';
+
+export const GET: RequestHandler = async ({ url }) => {
+ const username = url.searchParams.get('leetCodeUsername');
+ const query = `
+ query userProfileCalendar($username: String!) {
+ matchedUser(username: $username) {
+ username
+ userCalendar {
+ activeYears
+ streak
+ totalActiveDays
+ dccBadges {
+ timestamp
+ badge {
+ name
+ icon
+ }
+ }
+ }
+
+ profile {
+ ranking
+ }
+
+ submitStatsGlobal {
+ acSubmissionNum {
+ difficulty
+ count
+ }
+ }
+ }
+ }`;
+ const variables = {
+ username,
+ };
+
+ const response = await fetch('https://leetcode.com/graphql', {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ method: 'POST',
+ body: JSON.stringify({
+ operationName: 'userProfileCalendar',
+ query,
+ variables
+ })
+ });
+
+ const data = await response.json();
+ return new Response(JSON.stringify(data), {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ });
+};
\ No newline at end of file
diff --git a/src/routes/profile/+layout.server.ts b/src/routes/profile/+layout.server.ts
index a8a5f41..c1631d9 100644
--- a/src/routes/profile/+layout.server.ts
+++ b/src/routes/profile/+layout.server.ts
@@ -13,6 +13,7 @@ import { personalInformationSchema } from '$lib/schemas/personal-information';
import type { LayoutServerLoad } from '../$types';
import { chessComSchema } from '$lib/schemas/integration-chesscom';
import { cryptoSchema } from '$lib/schemas/crypto';
+import { leetCodeSchema } from '$lib/schemas/integration-leetcode';
// Define the user variable with a possible null
let user: User | null = null;
@@ -78,6 +79,10 @@ export const load: LayoutServerLoad = async (event) => {
where: { userId: user.githubId }
});
+ const leetCodeUsername = await prisma.integrationLeetCode.findFirst({
+ where: { userId: user.githubId }
+ });
+
const crypto = await prisma.cryptoWallets.findMany({
where: { userId: user.githubId }
});
@@ -97,6 +102,7 @@ export const load: LayoutServerLoad = async (event) => {
const personalInformationForm = await superValidate(zod(personalInformationSchema));
const chessComForm = await superValidate(zod(chessComSchema));
const cryptoForm = await superValidate(zod(cryptoSchema));
+ const leetCodeForm = await superValidate(zod(leetCodeSchema));
// Return data to the frontend
return {
@@ -109,6 +115,7 @@ export const load: LayoutServerLoad = async (event) => {
socials,
spotifyToken,
chessComUsername,
+ leetCodeUsername,
crypto,
form: linksForm,
skillsForm,
@@ -116,6 +123,7 @@ export const load: LayoutServerLoad = async (event) => {
socialsForm,
personalInformationForm,
chessComForm,
- cryptoForm
+ cryptoForm,
+ leetCodeForm
};
};
diff --git a/src/routes/profile/+page.svelte b/src/routes/profile/+page.svelte
index 4c5524a..3f57f51 100644
--- a/src/routes/profile/+page.svelte
+++ b/src/routes/profile/+page.svelte
@@ -75,6 +75,16 @@
{/if}
+
+ LeetCode
+
+ {#if data.leetCodeUsername}
+
+ {:else}
+ Not Linked
+ {/if}
+
+
diff --git a/src/routes/profile/integrations/+page.server.ts b/src/routes/profile/integrations/+page.server.ts
index 8510854..a80d630 100644
--- a/src/routes/profile/integrations/+page.server.ts
+++ b/src/routes/profile/integrations/+page.server.ts
@@ -9,6 +9,7 @@ import { getGitHubUserIdFromImageUrl } from '$lib/utils/getGithubIDFromImage';
import { createRecentActivity } from '$lib/utils/createRecentActivity';
import { unlinkSpotify } from '$lib/utils/spotify/unlinkSpotify';
import { chessComSchema } from '$lib/schemas/integration-chesscom';
+import { leetCodeSchema } from '$lib/schemas/integration-leetcode';
// Define the user variable with a possible null
let user: User | null = null;
@@ -23,7 +24,7 @@ export const load: PageServerLoad = async (event) => {
// Fetch the user from the database
user = await prisma.user.findUnique({
where: { githubId: userId }
- });
+ });
};
export const actions: Actions = {
@@ -156,5 +157,52 @@ export const actions: Actions = {
console.log(err);
return fail(500, { message: 'Something went wrong.' });
}
+ },
+
+ createLeetCode: async (event) => {
+ const form = await superValidate(event, zod(leetCodeSchema));
+ if (!form.valid) return fail(400, { form });
+
+ const { username } = form.data;
+
+ if (user) {
+ try {
+ await prisma.integrationLeetCode.create({
+ data: {
+ username,
+ userId: user.githubId
+ }
+ });
+
+ createRecentActivity(
+ 'LEETCODE_LINKED',
+ `Linked your LeetCode account (${username})`,
+ user.githubId
+ );
+ } catch (error) {
+ console.error(error);
+ throw new Error('Failed to create LeetCode integration');
+ }
+ }
+ return { form };
+ },
+
+ deleteLeetCode: async ({ url }) => {
+ try {
+ if (user) {
+ await prisma.integrationLeetCode.delete({
+ where: { userId: user.githubId }
+ });
+
+ createRecentActivity('LEETCODE_UNLINKED', `Unlinked your LeetCode account`, user.githubId);
+ }
+ } catch (error) {
+ console.error(error);
+ return fail(500, { message: 'Something went wrong.' });
+ }
}
+
+
+
+
};
diff --git a/src/routes/profile/integrations/+page.svelte b/src/routes/profile/integrations/+page.svelte
index 1a0f2e1..fc61365 100644
--- a/src/routes/profile/integrations/+page.svelte
+++ b/src/routes/profile/integrations/+page.svelte
@@ -10,7 +10,10 @@
import MusicPlayer from '$lib/components/Shared/MusicPlayer.svelte';
import ChessComForm from '$lib/components/MyProfile/ChessComForm.svelte';
import { IconChess, IconLink } from '@tabler/icons-svelte';
+ import { Braces } from 'lucide-svelte';
import ChessComStats from '$lib/components/MyProfile/ChessComStats.svelte';
+ import LeetCodeForm from '$lib/components/MyProfile/LeetCodeForm.svelte';
+ import LeetCodeStats from '$lib/components/MyProfile/LeetCodeStats.svelte';
export let data: PageData;
@@ -42,7 +45,6 @@
-
{#if data.chessComUsername}
@@ -108,4 +110,25 @@
+
+
+ {#if data.leetCodeUsername}
+
+
+
+ {:else}
+
+
+
+ {/if}
+
+