Skip to content

Commit

Permalink
Merge pull request #136 from s1lvax/dev
Browse files Browse the repository at this point in the history
New patch, CodeWars integration
  • Loading branch information
s1lvax authored Feb 5, 2025
2 parents 2afd468 + 7345f4b commit c8ff682
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 20 deletions.
13 changes: 12 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ model User {
spotifyToken SpotifyToken?
RecentActivity RecentActivity[]
IntegrationChessCom IntegrationChessCom?
IntegrationLeetCode IntegrationLeetCode?
IntegrationLeetCode IntegrationLeetCode?
IntegrationCodewars IntegrationCodewars?
CryptoWallets CryptoWallets[]
}

Expand Down Expand Up @@ -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])
Expand Down
37 changes: 37 additions & 0 deletions src/lib/components/MyProfile/CodewarsForm.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import * as Form from '$lib/components/ui/form';
import { Input } from '$lib/components/ui/input';
import { codewarsSchema, type CodewarsSchema } from '$lib/schemas/integration-codewars';
import { type SuperValidated, type Infer, superForm } from 'sveltekit-superforms';
import { zodClient } from 'sveltekit-superforms/adapters';
export let data: SuperValidated<Infer<CodewarsSchema>>;
const form = superForm(data, {
validators: zodClient(codewarsSchema),
resetForm: true
});
const { form: formData, enhance } = form;
</script>

<form
method="POST"
use:enhance
action="?/createCodewars"
class="flex items-center justify-between space-x-4"
>
<div class="flex items-start space-x-2">
<Form.Field {form} name="username">
<Form.Control let:attrs>
<Form.Label>Username</Form.Label>
<Input {...attrs} bind:value={$formData.username} />
</Form.Control>
<Form.FieldErrors />
</Form.Field>
<div class="space-y-2">
<span class="invisible block">a</span>
<Form.Button>Add</Form.Button>
</div>
</div>
</form>
78 changes: 78 additions & 0 deletions src/lib/components/MyProfile/CodewarsStats.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script lang="ts">
import { onMount } from 'svelte';
import type { CodewarsStats } from '$lib/types/Codewars';
import { Award, Medal, Hash, Code, Hammer } from 'lucide-svelte';
import * as Card from '$lib/components/ui/card';
import * as Table from '$lib/components/ui/table';
export let codewarsUsername: string;
let data: CodewarsStats;
let loading = true;
onMount(async () => {
const response = await fetch(`https://www.codewars.com/api/v1/users/${codewarsUsername}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
data = await response.json();
loading = false;
});
</script>

<Card.Root class="max-h-[400px] overflow-y-auto">
<Card.Header>
<Card.Title>Codewars</Card.Title>
<Card.Description>{codewarsUsername}'s Codewars stats</Card.Description>
</Card.Header>
<Card.Content>
{#if loading}
<p>Loading...</p>
{:else}
<!-- Metrics -->
<h4 class="text-lg font-semibold mb-4">Metrics</h4>
<Table.Root class="w-full">
<Table.Body>
<Table.Row class="flex items-center space-x-4 mb-2">
<Award class="text-red-500 w-6 h-6" />
<Table.Cell class="font-medium">Rank:</Table.Cell>
<Table.Cell class="text-{data.ranks.overall.color}-500">{data.ranks.overall.name}</Table.Cell>
</Table.Row>
<Table.Row class="flex items-center space-x-4 mb-2">
<Hash class="text-blue-500 w-6 h-6" />
<Table.Cell class="font-medium">Honor:</Table.Cell>
<Table.Cell>{data.honor}</Table.Cell>
</Table.Row>
<Table.Row class="flex items-center space-x-4">
<Medal class="text-yellow-500 w-6 h-6" />
<Table.Cell class="font-medium">Ranking:</Table.Cell>
<Table.Cell>{data.leaderboardPosition}</Table.Cell>
</Table.Row>
<Table.Row class="flex items-center space-x-4">
<Code class="text-green-500 w-6 h-6" />
<Table.Cell class="font-medium">Completed:</Table.Cell>
<Table.Cell>{data.codeChallenges.totalCompleted}</Table.Cell>
</Table.Row>
<Table.Row class="flex items-center space-x-4">
<Hammer class="text-purple-500 w-6 h-6" />
<Table.Cell class="font-medium">Created:</Table.Cell>
<Table.Cell>{data.codeChallenges.totalAuthored}</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>

<h4 class="text-lg font-semibold mb-4 mt-3">Languages</h4>
<Table.Root class="w-full">
<Table.Body>
<Table.Row class="flex items-center space-x-4 mb-2">
{#each Object.keys(data.ranks.languages) as language}
<Table.Cell>{language.charAt(0).toUpperCase() + language.slice(1)}</Table.Cell>
<Table.Cell class="text-{data.ranks.languages[language].color}-500">{data.ranks.languages[language].name}</Table.Cell>
{/each}
</Table.Row>
</Table.Body>
</Table.Root>
{/if}
</Card.Content>
</Card.Root>
6 changes: 6 additions & 0 deletions src/lib/components/PublicProfile/PublicProfile.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,6 +43,11 @@
{#if userData.leetCode != null}
<LeetCodeStats leetCodeUsername={userData.leetCode.username} />
{/if}

<!-- Codewars Stats Section -->
{#if userData.codewars != null}
<CodewarsStats codewarsUsername={userData.codewars.username} />
{/if}
</div>

<div class="flex flex-col items-center justify-center gap-4">
Expand Down
7 changes: 7 additions & 0 deletions src/lib/schemas/integration-codewars.ts
Original file line number Diff line number Diff line change
@@ -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;
32 changes: 32 additions & 0 deletions src/lib/types/Codewars.ts
Original file line number Diff line number Diff line change
@@ -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;
};
};
3 changes: 2 additions & 1 deletion src/lib/types/PublicProfile.ts
Original file line number Diff line number Diff line change
@@ -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 }>;
Expand All @@ -11,4 +11,5 @@ export interface PublicProfile {
chessComUsername: string | null;
crypto: CryptoWallets[];
leetCode: IntegrationLeetCode | null;
codewars: IntegrationCodewars | null;
}
2 changes: 2 additions & 0 deletions src/lib/utils/createRecentActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions src/routes/[username]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -76,8 +81,8 @@ export const load: PageServerLoad = async ({ params }) => {
hobbies,
crypto,
// TODO add leetCode to the userData
leetCode

leetCode,
codewars,
};

return {
Expand Down
10 changes: 9 additions & 1 deletion src/routes/profile/+layout.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 }
});
Expand All @@ -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 {
Expand All @@ -116,6 +122,7 @@ export const load: LayoutServerLoad = async (event) => {
spotifyToken,
chessComUsername,
leetCodeUsername,
codewarsUsername,
crypto,
form: linksForm,
skillsForm,
Expand All @@ -124,6 +131,7 @@ export const load: LayoutServerLoad = async (event) => {
personalInformationForm,
chessComForm,
cryptoForm,
leetCodeForm
leetCodeForm,
codewarsForm
};
};
70 changes: 55 additions & 15 deletions src/routes/profile/integrations/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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.' });
}
}




};
};
Loading

0 comments on commit c8ff682

Please sign in to comment.