diff --git a/package-lock.json b/package-lock.json
index 4b18a73..1c9e679 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@huggingface/inference": "^3.6.2",
"@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
"@prisma/client": "^6.5.0",
+ "agora-rtc-sdk-ng": "^4.23.2",
"barqode": "^0.0.2",
"better-auth": "^1.2.5",
"canvas": "^3.1.0",
@@ -67,6 +68,52 @@
"zod": "^3.24.2"
}
},
+ "node_modules/@agora-js/media": {
+ "version": "4.23.2",
+ "resolved": "https://registry.npmjs.org/@agora-js/media/-/media-4.23.2.tgz",
+ "integrity": "sha512-mWbl/ZjAw4tT22Ks0O2CJLMX3ht4uKPsDm2+fb5XOalrUVNzJOtwJA4E36d/o4ZfVtvwiJAOYtuTpoib8LlLgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/report": "4.23.2",
+ "@agora-js/shared": "4.23.2",
+ "agora-rte-extension": "^1.2.4",
+ "axios": "1.7.9",
+ "webrtc-adapter": "8.2.0"
+ }
+ },
+ "node_modules/@agora-js/media/node_modules/webrtc-adapter": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.0.tgz",
+ "integrity": "sha512-umxCMgedPAVq4Pe/jl3xmelLXLn4XZWFEMR5Iipb5wJ+k1xMX0yC4ZY9CueZUU1MjapFxai1tFGE7R/kotH6Ww==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "sdp": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
+ "node_modules/@agora-js/report": {
+ "version": "4.23.2",
+ "resolved": "https://registry.npmjs.org/@agora-js/report/-/report-4.23.2.tgz",
+ "integrity": "sha512-enHyWN5uF0RuS1crlHUlTfBZ7yy3n2zvSqt2odvsz4S1DZ0DlYQoCHFbr5CBOhN+Zb3bLEWxY3ej/2kUMqCQBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/shared": "4.23.2",
+ "axios": "1.7.9"
+ }
+ },
+ "node_modules/@agora-js/shared": {
+ "version": "4.23.2",
+ "resolved": "https://registry.npmjs.org/@agora-js/shared/-/shared-4.23.2.tgz",
+ "integrity": "sha512-DaeqskGUWero8SeqU0QeYEfJu4OcPIIltiQ0PRyMbyVa4HL7vCzr6gRZkef9sFMVyJPNe0QXL7gdX132kA2uhg==",
+ "license": "MIT",
+ "dependencies": {
+ "axios": "1.7.9",
+ "ua-parser-js": "^0.7.34"
+ }
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -2659,6 +2706,48 @@
"node": ">= 8.0.0"
}
},
+ "node_modules/agora-rtc-sdk-ng": {
+ "version": "4.23.2",
+ "resolved": "https://registry.npmjs.org/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.23.2.tgz",
+ "integrity": "sha512-uZcJVJ0g4PJfCqnlcP9kFU6y/2n9uIlRSljRFnK2FNLHo8nlB8Vc0pf5Gs4n75PmThC4u1G2FvL4k7iFau6boQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@agora-js/media": "4.23.2",
+ "@agora-js/report": "4.23.2",
+ "@agora-js/shared": "4.23.2",
+ "agora-rte-extension": "^1.2.4",
+ "axios": "1.7.9",
+ "formdata-polyfill": "^4.0.7",
+ "pako": "^2.1.0",
+ "ua-parser-js": "^0.7.34",
+ "webrtc-adapter": "8.2.0"
+ }
+ },
+ "node_modules/agora-rtc-sdk-ng/node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
+ "node_modules/agora-rtc-sdk-ng/node_modules/webrtc-adapter": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.0.tgz",
+ "integrity": "sha512-umxCMgedPAVq4Pe/jl3xmelLXLn4XZWFEMR5Iipb5wJ+k1xMX0yC4ZY9CueZUU1MjapFxai1tFGE7R/kotH6Ww==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "sdp": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
+ "node_modules/agora-rte-extension": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/agora-rte-extension/-/agora-rte-extension-1.2.4.tgz",
+ "integrity": "sha512-0ovZz1lbe30QraG1cU+ji7EnQ8aUu+Hf3F+a8xPml3wPOyUQEK6CTdxV9kMecr9t+fIDrGeW7wgJTsM1DQE7Nw==",
+ "license": "ISC"
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -2839,6 +2928,17 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/axios": {
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -4305,6 +4405,29 @@
}
}
},
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -4393,6 +4516,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@@ -4456,6 +4599,18 @@
"node": ">= 18"
}
},
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/formsnap": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.0.tgz",
@@ -6546,6 +6701,12 @@
"license": "MIT",
"optional": true
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/pump": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
@@ -6838,21 +6999,6 @@
"queue-microtask": "^1.2.2"
}
},
- "node_modules/runed": {
- "version": "0.16.3",
- "resolved": "https://registry.npmjs.org/runed/-/runed-0.16.3.tgz",
- "integrity": "sha512-4LJmDU9iGyCZLzNzNy0f0GId0/n/nN/VlhAX2t9PkmcSpxIEpc4Ads4HIUj4xp9UBZfZceRwtfLqaktK2eQJMw==",
- "funding": [
- "https://github.com/sponsors/huntabyte",
- "https://github.com/sponsors/tglide"
- ],
- "dependencies": {
- "esm-env": "^1.0.0"
- },
- "peerDependencies": {
- "svelte": "^5.0.0-next.1"
- }
- },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -8167,6 +8313,32 @@
"typescript": ">=4.8.4 <5.9.0"
}
},
+ "node_modules/ua-parser-js": {
+ "version": "0.7.40",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.40.tgz",
+ "integrity": "sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/uncrypto": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
diff --git a/package.json b/package.json
index 6572957..2eef1c8 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"@huggingface/inference": "^3.6.2",
"@mediapipe/tasks-vision": "^0.10.22-rc.20250304",
"@prisma/client": "^6.5.0",
+ "agora-rtc-sdk-ng": "^4.23.2",
"barqode": "^0.0.2",
"better-auth": "^1.2.5",
"canvas": "^3.1.0",
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 5520708..6745154 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -9,7 +9,7 @@ datasource db {
}
model User {
- id String @id @default(cuid())
+ id String @id @default(cuid())
name String
email String
emailVerified Boolean
@@ -20,8 +20,6 @@ model User {
accounts Account[]
Macros Macros[]
Onboarding Onboarding[]
- createdRooms Room[] @relation("RoomCreator")
- roomsJoined RoomParticipant[]
HeartRate HeartRate[]
Routines Routines[]
@@ -100,37 +98,6 @@ model Onboarding {
user User @relation(fields: [userId], references: [id])
}
-model Room {
- id String @id @default(cuid())
- name String?
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- creatorId String
- creator User @relation("RoomCreator", fields: [creatorId], references: [id])
- participants RoomParticipant[]
- status RoomStatus @default(STARTING)
-
- @@map("room")
-}
-
-enum RoomStatus {
- STARTING
- ACTIVE
- CLOSED
-}
-
-model RoomParticipant {
- id String @id @default(cuid())
- roomId String
- room Room @relation(fields: [roomId], references: [id], onDelete: Cascade)
- userId String
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- joinedAt DateTime @default(now())
-
- @@unique([roomId, userId])
- @@map("room_participant")
-}
-
model HeartRate {
id String @id @default(cuid())
user User @relation(fields: [userId], references: [id])
diff --git a/src/lib/agora.ts b/src/lib/agora.ts
new file mode 100644
index 0000000..702950d
--- /dev/null
+++ b/src/lib/agora.ts
@@ -0,0 +1,35 @@
+import AgoraRTC, { type ICameraVideoTrack, type IMicrophoneAudioTrack } from 'agora-rtc-sdk-ng';
+
+const appId = '989389d78f284ae4bf4524ac7e09e1b3'; // Replace with your Agora app ID
+
+export const agoraClient = AgoraRTC.createClient({
+ mode: 'rtc',
+ codec: 'vp8'
+});
+
+export async function createLocalTracks(): Promise<{
+ videoTrack: ICameraVideoTrack;
+}> {
+ const videoTrack = await AgoraRTC.createCameraVideoTrack();
+ return { videoTrack };
+}
+
+export async function joinChannel(channelName: string, uid: number) {
+ try {
+ await agoraClient.join(appId, channelName, null, uid);
+ const { videoTrack } = await createLocalTracks();
+ await agoraClient.publish([videoTrack]);
+ return { videoTrack };
+ } catch (error) {
+ console.error('Error joining channel:', error);
+ throw error;
+ }
+}
+
+export async function leaveChannel() {
+ agoraClient.remoteUsers.forEach((user) => {
+ const playerContainer = document.getElementById(`player-${user.uid}`);
+ playerContainer?.remove();
+ });
+ await agoraClient.leave();
+}
diff --git a/src/lib/components/ProfileCard.svelte b/src/lib/components/ProfileCard.svelte
new file mode 100644
index 0000000..e69de29
diff --git a/src/routes/(app)/(components)/chat-agent.svelte b/src/routes/(app)/(components)/chat-agent.svelte
new file mode 100644
index 0000000..6835b68
--- /dev/null
+++ b/src/routes/(app)/(components)/chat-agent.svelte
@@ -0,0 +1,614 @@
+
+
+
+
+
+
+
+
+{#if isOpen && !isMinimized}
+
+
+
+
+ AI
+
+
+
Fitness Assistant
+
+
+
+ Online
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if isCheckingOnboarding}
+
+
+
+
Loading your profile...
+
+
+ {:else if !hasCompletedOnboarding}
+
+
+
+ Onboarding Required
+
+ To get personalized fitness assistance, we need some information about you and your
+ fitness goals.
+
+
+
+
+ {:else}
+
+ {#each messages as message (message.id)}
+
+ {#if message.sender === 'bot'}
+
+
+ AI
+
+
+ {/if}
+
+
+ {#if message.sender === 'bot'}
+ {@html renderMessage(message)}
+ {:else}
+
{message.content}
+ {/if}
+
+ {formatTime(message.timestamp)}
+
+
+
+ {#if message.sender === 'user'}
+
+
+ You
+
+
+ {/if}
+
+ {/each}
+
+ {#if isLoading}
+
+ {/if}
+
+ {/if}
+
+
+
+
+
+
+
+{:else if isMinimized}
+
+
+
+
+ Chat minimized
+
+
+
+{/if}
diff --git a/src/routes/(app)/compete/+page.svelte b/src/routes/(app)/compete/+page.svelte
index 043a301..c75abb9 100644
--- a/src/routes/(app)/compete/+page.svelte
+++ b/src/routes/(app)/compete/+page.svelte
@@ -1,45 +1,34 @@
-
-
Compete
-
-
-
- Create a Room
-
-
-
-
-
-
-
-
-
-
-
-
- Join a Room
-
-
-
-
-
-
-
-
-
+
+
Compete
+
+
+
+
+
+
+
+
+ Join with Room Code
+
+
+
+
+
+
+
+
+
+
+
{#if errorMessage}
diff --git a/src/routes/(app)/compete/[roomId]/+page.svelte b/src/routes/(app)/compete/[roomId]/+page.svelte
index 8d0d9c6..101ee02 100644
--- a/src/routes/(app)/compete/[roomId]/+page.svelte
+++ b/src/routes/(app)/compete/[roomId]/+page.svelte
@@ -1,6 +1,8 @@
-
+
+
{#if loading}
-
Loading room details...
+
Connecting to room...
{:else if error}
{error}
-
+
+ {:else if isCompetitionActive}
+
+
+
+
+
+
+
+
+ Competition Streams
+
+
+
+
+
+
+
+
+
+ {#each Object.entries(remoteUsers) as [uid, _]}
+
+
+
+ {participants.find((p) => p.userId === uid)?.name || 'Participant'}
+
+
+ {/each}
+
+
+ {#if Object.keys(remoteUsers).length < 3}
+
+ {#each Array(3 - Object.keys(remoteUsers).length) as _}
+
+
Waiting for participant...
+
+ {/each}
+ {/if}
+
+
+
+
+
+
+
+
+
{:else}
+
- {room?.name || 'Competition Room'}
+ Competition Room
Room ID: {roomId}
@@ -158,9 +399,9 @@
- {#if participant.user.image}
+ {#if participant.image}
-
+
@@ -172,8 +413,8 @@
{/if}
- {participant.user.name}
- {#if participant.userId === room?.creatorId}
+ {participant.name}
+ {#if participant.userId === creatorId}
Host
{/if}
diff --git a/src/routes/(app)/compete/[roomId]/play/+page.svelte b/src/routes/(app)/compete/[roomId]/play/+page.svelte
deleted file mode 100644
index 4d8b451..0000000
--- a/src/routes/(app)/compete/[roomId]/play/+page.svelte
+++ /dev/null
@@ -1,555 +0,0 @@
-
-
-
- {#if loading}
-
-
-
-
Setting up competition room...
-
-
- {:else if error}
-
- {error}
-
-
-
-
- {:else}
-
-
Live Competition
-
-
-
-
-
- {#if localStream}
-
-
- You (Live) - Score: {participantScores[$session.data?.user.id || ''] || 0}
-
- {:else}
-
- {/if}
-
-
-
-
-
Leaderboard
-
-
-
- {#each getSortedParticipants() as participant, index}
- {#if participant.user.id !== $session.data?.user.id}
-
-
-
- {#if index < 3}
-
- {:else}
- {index + 1}
- {/if}
-
-
-
- {#if participant.user.image}
-
- {:else}
-
- {participant.user.name?.charAt(0) || '?'}
-
- {/if}
-
-
-
- {participant.user.name}
- Score: {participantScores[participant.user.id] || 0}
-
-
-
-
- {#if remoteStreams[participant.user.id]}
-
-
-
-
-
- {#if connectionStates[participant.user.id] === 'connected' || connectionStates[participant.user.id] === 'completed'}
-
-
- Connected
-
- {:else if connectionStates[participant.user.id] === 'disconnected'}
-
-
- Reconnecting...
-
- {:else if connectionStates[participant.user.id] === 'failed'}
-
-
- Connection failed
-
- {:else}
-
-
- {connectionStates[participant.user.id] || 'Connecting'}
-
- {/if}
-
-
-
- {#if streamInfo[participant.user.id]}
-
- V: {streamInfo[participant.user.id].videoTracks}
- A: {streamInfo[participant.user.id].audioTracks}
-
- {/if}
-
- {:else}
-
-
- {connectionStates[participant.user.id] === 'checking'
- ? 'Connecting...'
- : connectionStates[participant.user.id] === 'failed'
- ? 'Connection failed'
- : connectionStates[participant.user.id] === 'disconnected'
- ? 'Reconnecting...'
- : 'Waiting for connection'}
-
-
- {/if}
-
- {/if}
- {/each}
-
-
- {/if}
-
diff --git a/src/routes/api/rooms/+server.ts b/src/routes/api/rooms/+server.ts
deleted file mode 100644
index bde1084..0000000
--- a/src/routes/api/rooms/+server.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-import { json } from '@sveltejs/kit';
-import prisma from '$lib/prisma';
-import type { RequestHandler } from './$types';
-import { auth } from '$lib/auth';
-
-export const POST: RequestHandler = async ({ request }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const { name } = await request.json();
-
- const room = await prisma.room.create({
- data: {
- name: name || null,
- creatorId: session.user.id,
- participants: {
- create: {
- userId: session.user.id
- }
- },
- status: 'STARTING'
- }
- });
-
- return json(room);
- } catch (error) {
- console.error('Error creating room:', error);
- return json({ message: 'Failed to create room' }, { status: 500 });
- }
-};
-
-export const GET: RequestHandler = async ({ request }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const rooms = await prisma.room.findMany({
- where: {
- participants: {
- some: {
- userId: session.user.id
- }
- }
- },
- include: {
- creator: {
- select: {
- id: true,
- name: true,
- image: true
- }
- },
- _count: {
- select: {
- participants: true
- }
- }
- },
- orderBy: {
- createdAt: 'desc'
- }
- });
-
- return json({ rooms });
- } catch (error) {
- console.error('Error fetching rooms:', error);
- return json({ message: 'Failed to fetch rooms' }, { status: 500 });
- }
-};
diff --git a/src/routes/api/rooms/[roomId]/+server.ts b/src/routes/api/rooms/[roomId]/+server.ts
deleted file mode 100644
index 7b890ab..0000000
--- a/src/routes/api/rooms/[roomId]/+server.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { json } from '@sveltejs/kit';
-import prisma from '$lib/prisma';
-import type { RequestHandler } from './$types';
-import { auth } from '$lib/auth';
-
-export const GET: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
-
- const room = await prisma.room.findUnique({
- where: {
- id: roomId
- },
- include: {
- creator: {
- select: {
- id: true,
- name: true,
- image: true
- }
- }
- }
- });
-
- if (!room) {
- return json({ message: 'Room not found' }, { status: 404 });
- }
-
- const participants = await prisma.roomParticipant.findMany({
- where: {
- roomId,
- },
- include: {
- user: {
- select: {
- id: true,
- name: true,
- image: true
- }
- }
- },
- orderBy: {
- joinedAt: 'asc'
- }
- });
-
- return json({ room, participants });
- } catch (error) {
- console.error('Error fetching room details:', error);
- return json({ message: 'Failed to fetch room details' }, { status: 500 });
- }
-};
-
-export const DELETE: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
-
- const room = await prisma.room.findUnique({
- where: {
- id: roomId
- }
- });
-
- if (!room) {
- return json({ message: 'Room not found' }, { status: 404 });
- }
-
- if (room.creatorId !== session.user.id) {
- return json({ message: 'Only the room creator can delete this room' }, { status: 403 });
- }
-
- await prisma.room.update({
- where: {
- id: roomId
- },
- data: {
- status: 'CLOSED'
- }
- });
-
- return json({ message: 'Room successfully deleted' });
- } catch (error) {
- console.error('Error deleting room:', error);
- return json({ message: 'Failed to delete room' }, { status: 500 });
- }
-};
diff --git a/src/routes/api/rooms/[roomId]/join/+server.ts b/src/routes/api/rooms/[roomId]/join/+server.ts
deleted file mode 100644
index d5bec9f..0000000
--- a/src/routes/api/rooms/[roomId]/join/+server.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { auth } from '$lib/auth';
-import prisma from '$lib/prisma';
-import { json } from '@sveltejs/kit';
-import type { RequestHandler } from './$types';
-
-export const POST: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
-
- const room = await prisma.room.findUnique({
- where: {
- id: roomId,
- status: 'STARTING'
- }
- });
-
- if (!room) {
- return json({ message: 'Room not found or inactive' }, { status: 404 });
- }
-
- // Check if user is already a participant
- const existingParticipant = await prisma.roomParticipant.findUnique({
- where: {
- roomId_userId: {
- roomId,
- userId: session.user.id
- }
- }
- });
-
- if (existingParticipant) {
- return json({ message: 'You are already in this room' });
- }
-
- // Create new participant
- await prisma.roomParticipant.create({
- data: {
- roomId,
- userId: session.user.id
- }
- });
-
- return json({ message: 'Joined room successfully' });
- } catch (error) {
- console.error('Error joining room:', error);
- return json({ message: 'Failed to join room' }, { status: 500 });
- }
-};
diff --git a/src/routes/api/rooms/[roomId]/leave/+server.ts b/src/routes/api/rooms/[roomId]/leave/+server.ts
deleted file mode 100644
index 9ad26e6..0000000
--- a/src/routes/api/rooms/[roomId]/leave/+server.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import { json } from '@sveltejs/kit';
-import prisma from '$lib/prisma';
-import type { RequestHandler } from './$types';
-import { auth } from '$lib/auth';
-
-export const POST: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({ headers: request.headers });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
-
- const participant = await prisma.roomParticipant.findUnique({
- where: {
- roomId_userId: {
- roomId,
- userId: session.user.id
- }
- }
- });
-
- if (!participant) {
- return json({ message: 'You are not a participant in this room' }, { status: 404 });
- }
-
- // Check if user is the creator
- const room = await prisma.room.findUnique({
- where: {
- id: roomId
- }
- });
-
- if (room?.creatorId === session.user.id) {
- // If creator is leaving, mark the room as inactive
- await prisma.room.update({
- where: {
- id: roomId
- },
- data: {
- status: 'CLOSED'
- }
- });
-
- // Mark all participants as inactive
- await prisma.roomParticipant.deleteMany({
- where: { roomId }
- });
-
- return json({ message: 'Room closed successfully' });
- } else {
- // Just mark this participant as inactive
- await prisma.roomParticipant.delete({
- where: { id: participant.id }
- });
-
- return json({ message: 'Left room successfully' });
- }
- } catch (error) {
- console.error('Error leaving room:', error);
- return json({ message: 'Failed to leave room' }, { status: 500 });
- }
-};
diff --git a/src/routes/api/rooms/[roomId]/signaling/+server.ts b/src/routes/api/rooms/[roomId]/signaling/+server.ts
deleted file mode 100644
index b90ad67..0000000
--- a/src/routes/api/rooms/[roomId]/signaling/+server.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { json } from '@sveltejs/kit';
-import prisma from '$lib/prisma';
-import type { RequestHandler } from './$types';
-import { auth } from '$lib/auth';
-
-// In-memory store for pending messages
-// In a production app, you'd use Redis or another solution
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-const messageQueues: Record
= {};
-
-export const GET: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
- const userId = session.user.id;
-
- // Check if user is a participant
- const participant = await prisma.roomParticipant.findUnique({
- where: {
- roomId_userId: {
- roomId,
- userId
- }
- }
- });
-
- if (!participant) {
- return json({ message: 'You are not a participant in this room' }, { status: 404 });
- }
-
- // Set up SSE response
- const headers = new Headers({
- 'Content-Type': 'text/event-stream',
- 'Cache-Control': 'no-cache',
- Connection: 'keep-alive'
- });
-
- const stream = new ReadableStream({
- start(controller) {
- // Initialize message queue for this user
- if (!messageQueues[userId]) {
- messageQueues[userId] = [];
- }
-
- const intervalId = setInterval(() => {
- if (messageQueues[userId] && messageQueues[userId].length > 0) {
- const message = messageQueues[userId].shift();
- const data = `data: ${JSON.stringify(message)}\n\n`;
- controller.enqueue(new TextEncoder().encode(data));
- }
- }, 100);
-
- // Clean up when connection closes
- request.signal.addEventListener('abort', () => {
- clearInterval(intervalId);
- });
- }
- });
-
- return new Response(stream, { headers });
- } catch (error) {
- console.error('Error in signaling:', error);
- return json({ message: 'Failed to establish signaling' }, { status: 500 });
- }
-};
-
-export const POST: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
- const userId = session.user.id;
-
- // Check if user is a participant
- const participant = await prisma.roomParticipant.findUnique({
- where: {
- roomId_userId: {
- roomId,
- userId
- }
- }
- });
-
- if (!participant) {
- return json({ message: 'You are not a participant in this room' }, { status: 404 });
- }
-
- const message = await request.json();
-
- // Validate message
- if (!message.to || !message.type) {
- return json({ message: 'Invalid signaling message' }, { status: 400 });
- }
-
- // Add sender info
- message.from = userId;
-
- // Queue message for recipient
- if (!messageQueues[message.to]) {
- messageQueues[message.to] = [];
- }
- messageQueues[message.to].push(message);
-
- return json({ success: true });
- } catch (error) {
- console.error('Error in signaling:', error);
- return json({ message: 'Failed to send signaling message' }, { status: 500 });
- }
-};
diff --git a/src/routes/api/rooms/[roomId]/start/+server.ts b/src/routes/api/rooms/[roomId]/start/+server.ts
deleted file mode 100644
index 5ea1804..0000000
--- a/src/routes/api/rooms/[roomId]/start/+server.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { json } from '@sveltejs/kit';
-import prisma from '$lib/prisma';
-import type { RequestHandler } from './$types';
-import { auth } from '$lib/auth';
-
-export const POST: RequestHandler = async ({ request, params }) => {
- try {
- const session = await auth.api.getSession({
- headers: request.headers
- });
-
- if (!session) {
- return json({ message: 'Unauthorized' }, { status: 401 });
- }
-
- const roomId = params.roomId;
-
- // Check if the user is the room creator
- const room = await prisma.room.findUnique({
- where: {
- id: roomId,
- status: 'STARTING'
- }
- });
-
- if (!room) {
- return json({ message: 'Room not found' }, { status: 404 });
- }
-
- if (room.creatorId !== session.user.id) {
- return json({ message: 'Only the room creator can start the competition' }, { status: 403 });
- }
-
- // Update room status to indicate competition has started
- await prisma.room.update({
- where: {
- id: roomId
- },
- data: {
- status: 'ACTIVE'
- }
- });
-
- return json({ message: 'Competition started successfully' });
- } catch (error) {
- console.error('Error starting competition:', error);
- return json({ message: 'Failed to start competition' }, { status: 500 });
- }
-};