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} + +{:else if isMinimized} + +{/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 + + + +
+ +
+
+
+ You (Live) +
+
+ + + + {#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} -
-

Camera not available

-
- {/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 }); - } -};