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 @@ + + +
+
+ + + Username + + + + +
+ + Add +
+
+
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} +
+