From c4dbadd6601b1a8cdf10a63856f43ad0a67eb954 Mon Sep 17 00:00:00 2001
From: r4z0rbl8d3 <88277260+R4z0rBl8D3@users.noreply.github.com>
Date: Wed, 15 Jan 2025 19:40:58 -0500
Subject: [PATCH] Codewars Integration
---
prisma/schema.prisma | 13 +++-
.../components/MyProfile/CodewarsForm.svelte | 37 +++++++++
.../components/MyProfile/CodewarsStats.svelte | 78 +++++++++++++++++++
.../PublicProfile/PublicProfile.svelte | 6 ++
src/lib/schemas/integration-codewars.ts | 7 ++
src/lib/types/Codewars.ts | 32 ++++++++
src/lib/types/PublicProfile.ts | 3 +-
src/lib/utils/createRecentActivity.ts | 2 +
src/routes/[username]/+page.server.ts | 9 ++-
src/routes/profile/+layout.server.ts | 10 ++-
.../profile/integrations/+page.server.ts | 70 +++++++++++++----
src/routes/profile/integrations/+page.svelte | 24 ++++++
12 files changed, 271 insertions(+), 20 deletions(-)
create mode 100644 src/lib/components/MyProfile/CodewarsForm.svelte
create mode 100644 src/lib/components/MyProfile/CodewarsStats.svelte
create mode 100644 src/lib/schemas/integration-codewars.ts
create mode 100644 src/lib/types/Codewars.ts
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 41fd067..b1e9283 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -29,7 +29,8 @@ model User {
spotifyToken SpotifyToken?
RecentActivity RecentActivity[]
IntegrationChessCom IntegrationChessCom?
- IntegrationLeetCode IntegrationLeetCode?
+ IntegrationLeetCode IntegrationLeetCode?
+ IntegrationCodewars IntegrationCodewars?
CryptoWallets CryptoWallets[]
}
@@ -119,6 +120,16 @@ model IntegrationLeetCode {
updatedAt DateTime @updatedAt
}
+model IntegrationCodewars {
+ 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/CodewarsForm.svelte b/src/lib/components/MyProfile/CodewarsForm.svelte
new file mode 100644
index 0000000..c3c2f79
--- /dev/null
+++ b/src/lib/components/MyProfile/CodewarsForm.svelte
@@ -0,0 +1,37 @@
+
+
+
diff --git a/src/lib/components/MyProfile/CodewarsStats.svelte b/src/lib/components/MyProfile/CodewarsStats.svelte
new file mode 100644
index 0000000..728f87c
--- /dev/null
+++ b/src/lib/components/MyProfile/CodewarsStats.svelte
@@ -0,0 +1,78 @@
+
+
+
+
+ Codewars
+ {codewarsUsername}'s Codewars stats
+
+
+ {#if loading}
+ Loading...
+ {:else}
+
+ Metrics
+
+
+
+
+ Rank:
+ {data.ranks.overall.name}
+
+
+
+ Honor:
+ {data.honor}
+
+
+
+ Ranking:
+ {data.leaderboardPosition}
+
+
+
+ Completed:
+ {data.codeChallenges.totalCompleted}
+
+
+
+ Created:
+ {data.codeChallenges.totalAuthored}
+
+
+
+
+ Languages
+
+
+
+ {#each Object.keys(data.ranks.languages) as language}
+ {language.charAt(0).toUpperCase() + language.slice(1)}
+ {data.ranks.languages[language].name}
+ {/each}
+
+
+
+ {/if}
+
+
diff --git a/src/lib/components/PublicProfile/PublicProfile.svelte b/src/lib/components/PublicProfile/PublicProfile.svelte
index 9e53191..876f9d2 100644
--- a/src/lib/components/PublicProfile/PublicProfile.svelte
+++ b/src/lib/components/PublicProfile/PublicProfile.svelte
@@ -9,6 +9,7 @@
import { Separator } from '$lib/components//ui/separator';
import ChessComStats from '$lib/components/MyProfile/ChessComStats.svelte';
import LeetCodeStats from '$lib/components/MyProfile/LeetCodeStats.svelte';
+ import CodewarsStats from '$lib/components/MyProfile/CodewarsStats.svelte';
// Accept userData as a prop
export let userData: PublicProfile;
@@ -42,6 +43,11 @@
{#if userData.leetCode != null}
{/if}
+
+
+ {#if userData.codewars != null}
+
+ {/if}
diff --git a/src/lib/schemas/integration-codewars.ts b/src/lib/schemas/integration-codewars.ts
new file mode 100644
index 0000000..97073ac
--- /dev/null
+++ b/src/lib/schemas/integration-codewars.ts
@@ -0,0 +1,7 @@
+import { z } from 'zod';
+
+export const codewarsSchema = z.object({
+ username: z.string().min(3).max(20)
+});
+
+export type CodewarsSchema = typeof codewarsSchema;
\ No newline at end of file
diff --git a/src/lib/types/Codewars.ts b/src/lib/types/Codewars.ts
new file mode 100644
index 0000000..b679464
--- /dev/null
+++ b/src/lib/types/Codewars.ts
@@ -0,0 +1,32 @@
+import { string } from "zod";
+
+export type CodewarsStats = {
+ username: string;
+ name: string;
+ honor: number;
+ clan: string | null;
+ leaderboardPosition: number;
+ skills: string[] | null;
+
+ ranks: {
+ overall: {
+ rank: number;
+ name: string;
+ color: string;
+ score: number;
+ };
+ languages: {
+ [key: string]: {
+ rank: number;
+ name: string;
+ color: string;
+ score: number;
+ };
+ };
+ };
+
+ codeChallenges: {
+ totalAuthored: number;
+ totalCompleted: number;
+ };
+};
\ No newline at end of file
diff --git a/src/lib/types/PublicProfile.ts b/src/lib/types/PublicProfile.ts
index 6e79b0f..48fd6e2 100644
--- a/src/lib/types/PublicProfile.ts
+++ b/src/lib/types/PublicProfile.ts
@@ -1,4 +1,4 @@
-import type { CryptoWallets, PersonalInformation, Social, IntegrationLeetCode} from '@prisma/client';
+import type { CryptoWallets, PersonalInformation, Social, IntegrationLeetCode, IntegrationCodewars} from '@prisma/client';
export interface PublicProfile {
links: Array<{ title: string; url: string }>;
@@ -11,4 +11,5 @@ export interface PublicProfile {
chessComUsername: string | null;
crypto: CryptoWallets[];
leetCode: IntegrationLeetCode | null;
+ codewars: IntegrationCodewars | null;
}
diff --git a/src/lib/utils/createRecentActivity.ts b/src/lib/utils/createRecentActivity.ts
index 5b7d320..706f150 100644
--- a/src/lib/utils/createRecentActivity.ts
+++ b/src/lib/utils/createRecentActivity.ts
@@ -18,6 +18,8 @@ export const createRecentActivity = async (
| 'PERSONAL_INFORMATION_UPDATED'
| 'CRYPTO_CREATED'
| 'CRYPTO_DELETED'
+ | 'CODEWARS_LINKED'
+ | 'CODEWARS_UNLINKED'
| 'LEETCODE_LINKED'
| 'LEETCODE_UNLINKED',
activityDescription: string,
diff --git a/src/routes/[username]/+page.server.ts b/src/routes/[username]/+page.server.ts
index e1da7ed..5637863 100644
--- a/src/routes/[username]/+page.server.ts
+++ b/src/routes/[username]/+page.server.ts
@@ -60,10 +60,15 @@ 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 codewars = await prisma.integrationCodewars.findUnique({
+ where: { userId: user.githubId }
+ });
+
const userData: PublicProfile = {
links,
@@ -76,8 +81,8 @@ export const load: PageServerLoad = async ({ params }) => {
hobbies,
crypto,
// TODO add leetCode to the userData
- leetCode
-
+ leetCode,
+ codewars,
};
return {
diff --git a/src/routes/profile/+layout.server.ts b/src/routes/profile/+layout.server.ts
index c1631d9..369e6df 100644
--- a/src/routes/profile/+layout.server.ts
+++ b/src/routes/profile/+layout.server.ts
@@ -14,6 +14,7 @@ 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';
+import { codewarsSchema } from '$lib/schemas/integration-codewars';
// Define the user variable with a possible null
let user: User | null = null;
@@ -83,6 +84,10 @@ export const load: LayoutServerLoad = async (event) => {
where: { userId: user.githubId }
});
+ const codewarsUsername = await prisma.integrationCodewars.findFirst({
+ where: { userId: user.githubId }
+ });
+
const crypto = await prisma.cryptoWallets.findMany({
where: { userId: user.githubId }
});
@@ -103,6 +108,7 @@ export const load: LayoutServerLoad = async (event) => {
const chessComForm = await superValidate(zod(chessComSchema));
const cryptoForm = await superValidate(zod(cryptoSchema));
const leetCodeForm = await superValidate(zod(leetCodeSchema));
+ const codewarsForm = await superValidate(zod(codewarsSchema));
// Return data to the frontend
return {
@@ -116,6 +122,7 @@ export const load: LayoutServerLoad = async (event) => {
spotifyToken,
chessComUsername,
leetCodeUsername,
+ codewarsUsername,
crypto,
form: linksForm,
skillsForm,
@@ -124,6 +131,7 @@ export const load: LayoutServerLoad = async (event) => {
personalInformationForm,
chessComForm,
cryptoForm,
- leetCodeForm
+ leetCodeForm,
+ codewarsForm
};
};
diff --git a/src/routes/profile/integrations/+page.server.ts b/src/routes/profile/integrations/+page.server.ts
index a80d630..0d5a843 100644
--- a/src/routes/profile/integrations/+page.server.ts
+++ b/src/routes/profile/integrations/+page.server.ts
@@ -10,6 +10,7 @@ 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';
+import { codewarsSchema } from '$lib/schemas/integration-codewars';
// Define the user variable with a possible null
let user: User | null = null;
@@ -159,6 +160,49 @@ export const actions: Actions = {
}
},
+ createCodewars: async (event) => {
+ const form = await superValidate(event, zod(codewarsSchema));
+ if (!form.valid) return fail(400, { form });
+
+ const { username } = form.data;
+
+ if (user) {
+ try {
+ await prisma.integrationCodewars.create({
+ data: {
+ username,
+ userId: user.githubId
+ }
+ });
+
+ createRecentActivity(
+ 'CODEWARS_LINKED',
+ `Linked your Codewars account (${username})`,
+ user.githubId
+ );
+ } catch (error) {
+ console.error(error);
+ throw new Error('Failed to create Codewars integration');
+ }
+ }
+ return { form };
+ },
+
+ deleteCodewars: async ({ url }) => {
+ try {
+ if (user) {
+ await prisma.integrationCodewars.delete({
+ where: { userId: user.githubId }
+ });
+
+ createRecentActivity('CODEWARS_UNLINKED', `Unlinked your Codewars account`, user.githubId);
+ }
+ } catch (error) {
+ console.error(error);
+ return fail(500, { message: 'Something went wrong.' });
+ }
+ },
+
createLeetCode: async (event) => {
const form = await superValidate(event, zod(leetCodeSchema));
if (!form.valid) return fail(400, { form });
@@ -188,21 +232,17 @@ export const actions: Actions = {
},
deleteLeetCode: async ({ url }) => {
- try {
- if (user) {
- await prisma.integrationLeetCode.delete({
- where: { userId: user.githubId }
- });
+ try {
+ if (user) {
+ await prisma.integrationLeetCode.delete({
+ where: { userId: user.githubId }
+ });
- createRecentActivity('LEETCODE_UNLINKED', `Unlinked your LeetCode account`, user.githubId);
+ createRecentActivity('LEETCODE_UNLINKED', `Unlinked your LeetCode account`, user.githubId);
+ }
+ } catch (error) {
+ console.error(error);
+ return fail(500, { message: 'Something went wrong.' });
}
- } catch (error) {
- console.error(error);
- return fail(500, { message: 'Something went wrong.' });
- }
}
-
-
-
-
-};
+};
\ No newline at end of file
diff --git a/src/routes/profile/integrations/+page.svelte b/src/routes/profile/integrations/+page.svelte
index fc61365..dde28bb 100644
--- a/src/routes/profile/integrations/+page.svelte
+++ b/src/routes/profile/integrations/+page.svelte
@@ -14,6 +14,8 @@
import ChessComStats from '$lib/components/MyProfile/ChessComStats.svelte';
import LeetCodeForm from '$lib/components/MyProfile/LeetCodeForm.svelte';
import LeetCodeStats from '$lib/components/MyProfile/LeetCodeStats.svelte';
+ import CodewarsForm from '$lib/components/MyProfile/CodewarsForm.svelte';
+ import CodewarsStats from '$lib/components/MyProfile/CodewarsStats.svelte';
export let data: PageData;
@@ -131,4 +133,26 @@
{/if}
+
+
+
+ {#if data.codewarsUsername}
+
+
+
+ {:else}
+
+
+
+ {/if}
+
+