diff --git a/package-lock.json b/package-lock.json index 8cf6083..77c3dd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@preline/dropdown": "^3.0.1", "@preline/tooltip": "^3.0.0", - "@prisma/client": "^6.7.0", + "@prisma/client": "^6.9.0", "csgo-sharecode": "^3.1.2", "datatables.net": "^2.2.2", "date-fns": "^4.1.0", @@ -51,7 +51,7 @@ "@types/ws": "^8.18.1", "eslint": "^9", "eslint-config-next": "15.3.0", - "prisma": "^6.7.0", + "prisma": "^6.9.0", "tailwindcss": "^4.1.4", "ts-node": "^10.9.2", "tsx": "^4.19.4", @@ -1522,9 +1522,9 @@ "license": "Licensed under MIT and Preline UI Fair Use License" }, "node_modules/@prisma/client": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz", - "integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz", + "integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -1544,64 +1544,63 @@ } }, "node_modules/@prisma/config": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz", - "integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz", + "integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "esbuild": ">=0.12 <1", - "esbuild-register": "3.6.0" + "jiti": "2.4.2" } }, "node_modules/@prisma/debug": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz", - "integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz", + "integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz", - "integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.9.0.tgz", + "integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0", - "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "@prisma/fetch-engine": "6.7.0", - "@prisma/get-platform": "6.7.0" + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/fetch-engine": "6.9.0", + "@prisma/get-platform": "6.9.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz", - "integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==", + "version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz", + "integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz", - "integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz", + "integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0", - "@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed", - "@prisma/get-platform": "6.7.0" + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/get-platform": "6.9.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz", - "integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz", + "integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.7.0" + "@prisma/debug": "6.9.0" } }, "node_modules/@rtsao/scc": { @@ -3159,7 +3158,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3477,7 +3476,7 @@ "version": "0.25.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -3514,19 +3513,6 @@ "@esbuild/win32-x64": "0.25.3" } }, - "node_modules/esbuild-register": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", - "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.4" - }, - "peerDependencies": { - "esbuild": ">=0.12 <1" - } - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -4988,7 +4974,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -5553,7 +5539,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -6223,15 +6209,15 @@ "peer": true }, "node_modules/prisma": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz", - "integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.9.0.tgz", + "integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.7.0", - "@prisma/engines": "6.7.0" + "@prisma/config": "6.9.0", + "@prisma/engines": "6.9.0" }, "bin": { "prisma": "build/index.js" @@ -6239,9 +6225,6 @@ "engines": { "node": ">=18.18" }, - "optionalDependencies": { - "fsevents": "2.3.3" - }, "peerDependencies": { "typescript": ">=5.1.0" }, diff --git a/package.json b/package.json index 04ca1c2..c70ef65 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@preline/dropdown": "^3.0.1", "@preline/tooltip": "^3.0.0", - "@prisma/client": "^6.7.0", + "@prisma/client": "^6.9.0", "csgo-sharecode": "^3.1.2", "datatables.net": "^2.2.2", "date-fns": "^4.1.0", @@ -54,7 +54,7 @@ "@types/ws": "^8.18.1", "eslint": "^9", "eslint-config-next": "15.3.0", - "prisma": "^6.7.0", + "prisma": "^6.9.0", "tailwindcss": "^4.1.4", "ts-node": "^10.9.2", "tsx": "^4.19.4", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 173c2dd..3cb4aa8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -54,7 +54,7 @@ model Team { } model Match { - matchId BigInt @id @default(autoincrement()) + id String @id @default(uuid()) teamAId String? teamBId String? matchDate DateTime @@ -78,11 +78,11 @@ model Match { model MatchPlayer { id String @id @default(cuid()) - matchId BigInt + matchId String steamId String teamId String? - match Match @relation(fields: [matchId], references: [matchId]) + match Match @relation(fields: [matchId], references: [id]) // 👈 id statt matchId user User @relation(fields: [steamId], references: [steamId]) team Team? @relation(fields: [teamId], references: [id]) stats MatchPlayerStats? @@ -92,6 +92,7 @@ model MatchPlayer { @@unique([matchId, steamId]) } + model MatchPlayerStats { id String @id @default(cuid()) matchPlayerId String @unique @@ -123,17 +124,18 @@ model MatchPlayerStats { model DemoFile { id String @id @default(cuid()) - matchId BigInt @unique + matchId String @unique steamId String fileName String @unique filePath String parsed Boolean @default(false) createdAt DateTime @default(now()) - match Match @relation(fields: [matchId], references: [matchId]) + match Match @relation(fields: [matchId], references: [id]) // 👈 id statt matchId user User @relation(fields: [steamId], references: [steamId]) } + model Invitation { id String @id @default(cuid()) userId String @@ -163,10 +165,11 @@ model CS2MatchRequest { id String @id @default(cuid()) userId String steamId String - matchId BigInt + matchId String reservationId BigInt tvPort BigInt processed Boolean @default(false) + failed Boolean @default(false) createdAt DateTime @default(now()) user User @relation("MatchRequests", fields: [userId], references: [steamId]) @@ -174,11 +177,12 @@ model CS2MatchRequest { @@unique([steamId, matchId]) } + model PremierRankHistory { id String @id @default(cuid()) userId String steamId String - matchId BigInt? + matchId String? // optionaler String rankOld Int rankNew Int @@ -187,5 +191,5 @@ model PremierRankHistory { createdAt DateTime @default(now()) user User @relation("UserRankHistory", fields: [userId], references: [steamId]) - match Match? @relation("MatchRankHistory", fields: [matchId], references: [matchId]) -} \ No newline at end of file + match Match? @relation("MatchRankHistory", fields: [matchId], references: [id]) // 👈 id statt matchId +} diff --git a/public/assets/img/logos/cs2.webp b/public/assets/img/logos/cs2.webp new file mode 100644 index 0000000..5d89148 Binary files /dev/null and b/public/assets/img/logos/cs2.webp differ diff --git a/public/assets/img/maps/ar_baggage.png b/public/assets/img/maps/ar_baggage.png new file mode 100644 index 0000000..6579a42 Binary files /dev/null and b/public/assets/img/maps/ar_baggage.png differ diff --git a/public/assets/img/maps/ar_pool_day.png b/public/assets/img/maps/ar_pool_day.png new file mode 100644 index 0000000..b830b22 Binary files /dev/null and b/public/assets/img/maps/ar_pool_day.png differ diff --git a/public/assets/img/maps/ar_shoots.png b/public/assets/img/maps/ar_shoots.png new file mode 100644 index 0000000..e25d042 Binary files /dev/null and b/public/assets/img/maps/ar_shoots.png differ diff --git a/public/assets/img/maps/cs_agency.png b/public/assets/img/maps/cs_agency.png new file mode 100644 index 0000000..ca9682b Binary files /dev/null and b/public/assets/img/maps/cs_agency.png differ diff --git a/public/assets/img/maps/cs_italy.png b/public/assets/img/maps/cs_italy.png new file mode 100644 index 0000000..0dc55cc Binary files /dev/null and b/public/assets/img/maps/cs_italy.png differ diff --git a/public/assets/img/maps/cs_office.png b/public/assets/img/maps/cs_office.png new file mode 100644 index 0000000..c85e2ac Binary files /dev/null and b/public/assets/img/maps/cs_office.png differ diff --git a/public/assets/img/maps/de_ancient.png b/public/assets/img/maps/de_ancient.png new file mode 100644 index 0000000..0314e23 Binary files /dev/null and b/public/assets/img/maps/de_ancient.png differ diff --git a/public/assets/img/maps/de_anubis.png b/public/assets/img/maps/de_anubis.png new file mode 100644 index 0000000..6b9a1c9 Binary files /dev/null and b/public/assets/img/maps/de_anubis.png differ diff --git a/public/assets/img/maps/de_brewery.png b/public/assets/img/maps/de_brewery.png new file mode 100644 index 0000000..32687e8 Binary files /dev/null and b/public/assets/img/maps/de_brewery.png differ diff --git a/public/assets/img/maps/de_dogtown.png b/public/assets/img/maps/de_dogtown.png new file mode 100644 index 0000000..728121a Binary files /dev/null and b/public/assets/img/maps/de_dogtown.png differ diff --git a/public/assets/img/maps/de_dust2.png b/public/assets/img/maps/de_dust2.png new file mode 100644 index 0000000..c41749f Binary files /dev/null and b/public/assets/img/maps/de_dust2.png differ diff --git a/public/assets/img/maps/de_grail.png b/public/assets/img/maps/de_grail.png new file mode 100644 index 0000000..d91cf9d Binary files /dev/null and b/public/assets/img/maps/de_grail.png differ diff --git a/public/assets/img/maps/de_inferno.png b/public/assets/img/maps/de_inferno.png new file mode 100644 index 0000000..0faed7e Binary files /dev/null and b/public/assets/img/maps/de_inferno.png differ diff --git a/public/assets/img/maps/de_jura.png b/public/assets/img/maps/de_jura.png new file mode 100644 index 0000000..784ba02 Binary files /dev/null and b/public/assets/img/maps/de_jura.png differ diff --git a/public/assets/img/maps/de_mirage.png b/public/assets/img/maps/de_mirage.png new file mode 100644 index 0000000..f7fd929 Binary files /dev/null and b/public/assets/img/maps/de_mirage.png differ diff --git a/public/assets/img/maps/de_nuke.png b/public/assets/img/maps/de_nuke.png new file mode 100644 index 0000000..354d329 Binary files /dev/null and b/public/assets/img/maps/de_nuke.png differ diff --git a/public/assets/img/maps/de_overpass.png b/public/assets/img/maps/de_overpass.png new file mode 100644 index 0000000..f643798 Binary files /dev/null and b/public/assets/img/maps/de_overpass.png differ diff --git a/public/assets/img/maps/de_train.png b/public/assets/img/maps/de_train.png new file mode 100644 index 0000000..54b3a7d Binary files /dev/null and b/public/assets/img/maps/de_train.png differ diff --git a/public/assets/img/maps/de_vertigo.png b/public/assets/img/maps/de_vertigo.png new file mode 100644 index 0000000..2d7e716 Binary files /dev/null and b/public/assets/img/maps/de_vertigo.png differ diff --git a/public/assets/img/maps/lobby_mapveto.png b/public/assets/img/maps/lobby_mapveto.png new file mode 100644 index 0000000..712db99 Binary files /dev/null and b/public/assets/img/maps/lobby_mapveto.png differ diff --git a/src/app/api/cs2/getNextCode/route.ts b/src/app/api/cs2/getNextCode/route.ts index 4070752..c000e82 100644 --- a/src/app/api/cs2/getNextCode/route.ts +++ b/src/app/api/cs2/getNextCode/route.ts @@ -1,36 +1,67 @@ -import { NextRequest, NextResponse } from 'next/server' -import { getServerSession } from 'next-auth' -import { authOptions } from '@/app/lib/auth' -import { prisma } from '@/app/lib/prisma' -import { decrypt, encrypt } from '@/app/lib/crypto' -import { decodeMatchShareCode, MatchInformation } from 'csgo-sharecode'; +import { NextRequest, NextResponse } from 'next/server'; +import { prisma } from '@/app/lib/prisma'; +import { decodeMatchShareCode } from 'csgo-sharecode'; +import { decrypt } from '@/app/lib/crypto'; -function delay(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)) -} +// Maximal 30 Tage gültig +const EXPIRY_DAYS = 30; export async function GET(req: NextRequest) { - const session = await getServerSession(authOptions(req)) - let steamId = session?.user?.steamId ?? req.headers.get('x-steamid') ?? undefined; + const steamId = req.headers.get('x-steamid') ?? undefined; if (!steamId) { return NextResponse.json({ valid: false, reason: 'Missing steamId' }); } + const user = await prisma.user.findUnique({ + where: { steamId }, + select: { + authCode: true, + lastKnownShareCode: true, + lastKnownShareCodeDate: true, + }, + }); + + if (!user?.authCode || !user.lastKnownShareCode) { + return NextResponse.json({ valid: false, reason: 'missing-sharecode' }); + } + + const isExpired = + user.lastKnownShareCodeDate && + new Date().getTime() - new Date(user.lastKnownShareCodeDate).getTime() > + EXPIRY_DAYS * 24 * 60 * 60 * 1000; + + if (isExpired) { + return NextResponse.json({ valid: false, reason: 'expired' }); + } + + return handleShareCodeRequest( + steamId, + decrypt(user.authCode), + user.lastKnownShareCode + ); +} + +export async function POST(req: NextRequest) { + const body = await req.json(); + const steamId: string = body.steamId; + const authCode: string = body.authCode; + const currentCode: string = body.currentCode; + + if (!steamId || !authCode || !currentCode) { + return NextResponse.json({ valid: false, error: 'Missing parameters' }); + } + + return handleShareCodeRequest(steamId, authCode, currentCode); +} + +async function handleShareCodeRequest( + steamId: string, + authCode: string, + knownCode: string +) { try { - const user = await prisma.user.findUnique({ - where: { steamId }, - select: { authCode: true, lastKnownShareCode: true }, - }); - - if (!user?.authCode || !user.lastKnownShareCode) { - return NextResponse.json({ valid: false }); - } - - const decryptedAuthCode = decrypt(user.authCode); - - // Nur EINEN nächsten Code abrufen - const url = `https://api.steampowered.com/ICSGOPlayers_730/GetNextMatchSharingCode/v1?key=${process.env.STEAM_API_KEY}&steamid=${steamId}&steamidkey=${decryptedAuthCode}&knowncode=${user.lastKnownShareCode}`; + const url = `https://api.steampowered.com/ICSGOPlayers_730/GetNextMatchSharingCode/v1?key=${process.env.STEAM_API_KEY}&steamid=${steamId}&steamidkey=${authCode}&knowncode=${knownCode}`; const res = await fetch(url); const data = await res.json(); @@ -41,42 +72,29 @@ export async function GET(req: NextRequest) { return NextResponse.json({ valid: true, nextCode: null }); } - // MatchInfo extrahieren & speichern const matchInfo = decodeMatchShareCode(nextCode); await prisma.cS2MatchRequest.upsert({ where: { steamId_matchId: { steamId, - matchId: matchInfo.matchId, + matchId: matchInfo.matchId.toString(), }, }, update: {}, create: { userId: steamId, steamId: steamId, - matchId: matchInfo.matchId, + matchId: matchInfo.matchId.toString(), reservationId: matchInfo.reservationId, tvPort: matchInfo.tvPort, }, }); - return NextResponse.json({ - valid: true, - nextCode, - }); - + return NextResponse.json({ valid: true, nextCode }); } catch (err) { - if (err instanceof Error && err.message === 'INVALID_CODE') { - return NextResponse.json({ - valid: false, - error: 'Invalid authCode or knownCode (veraltet oder ungültig)', - }); - } - return NextResponse.json({ valid: false, error: err instanceof Error ? err.message : String(err), }); } } - diff --git a/src/app/api/cs2/sharecode/route.ts b/src/app/api/cs2/sharecode/route.ts index 12187cd..7dd4a9c 100644 --- a/src/app/api/cs2/sharecode/route.ts +++ b/src/app/api/cs2/sharecode/route.ts @@ -1,40 +1,13 @@ +// /api/cs2/sharecode/route.ts + import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth' import { authOptions } from '@/app/lib/auth' import { prisma } from '@/app/lib/prisma' -import { encrypt, decrypt } from '@/app/lib/crypto' +import { decrypt, encrypt } from '@/app/lib/crypto' -export async function PUT(req: NextRequest) { - const session = await getServerSession(authOptions(req)) - const steamId = session?.user?.steamId - - if (!steamId) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { authCode, lastKnownShareCode } = await req.json() - - - if (!authCode || typeof authCode !== 'string') { - return NextResponse.json({ error: 'Ungültiger Auth Code' }, { status: 400 }) - } - - try { - await prisma.user.update({ - where: { steamId }, - data: { - authCode: encrypt(authCode), - lastKnownShareCode: lastKnownShareCode || undefined, - lastKnownShareCodeDate: lastKnownShareCode ? new Date() : undefined, - }, - }) - - return new NextResponse(null, { status: 204 }) - } catch (error) { - console.error('Fehler beim Speichern:', error) - return NextResponse.json({ error: 'Fehler beim Speichern der Codes' }, { status: 500 }) - } -} +// Maximal 30 Tage gültig +const EXPIRY_DAYS = 30 export async function GET(req: NextRequest) { const session = await getServerSession(authOptions(req)) @@ -54,13 +27,62 @@ export async function GET(req: NextRequest) { }, }) + const authCode = user?.authCode ? decrypt(user.authCode) : null + const lastKnownShareCode = user?.lastKnownShareCode ?? null + const lastKnownShareCodeDate = user?.lastKnownShareCodeDate ?? null + + let reason: 'expired' | null = null + + if ( + lastKnownShareCodeDate && + new Date().getTime() - new Date(lastKnownShareCodeDate).getTime() > EXPIRY_DAYS * 24 * 60 * 60 * 1000 + ) { + reason = 'expired' + } + return NextResponse.json({ - authCode: user?.authCode ? decrypt(user.authCode) : null, - lastKnownShareCode: user?.lastKnownShareCode ?? null, - lastKnownShareCodeDate: user?.lastKnownShareCodeDate ?? null, + authCode, + lastKnownShareCode, + lastKnownShareCodeDate, + reason, }) } catch (error) { - console.error('Fehler beim Abrufen:', error) - return NextResponse.json({ error: 'Fehler beim Abrufen der Codes' }, { status: 500 }) + console.error('[GET /api/cs2/sharecode]', error) + return NextResponse.json({ error: 'Fehler beim Abrufen' }, { status: 500 }) + } +} + +export async function PUT(req: NextRequest) { + const session = await getServerSession(authOptions(req)) + const steamId = session?.user?.steamId + + if (!steamId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const { authCode, lastKnownShareCode } = await req.json() + + // Optional: zusätzliche Validierung für authCode + const isValidAuthCode = !authCode || /^[A-Z0-9]{4}-[A-Z0-9]{5}-[A-Z0-9]{4}$/.test(authCode) + const isValidShareCode = !lastKnownShareCode || /^CSGO(-[a-zA-Z0-9]{5}){5}$/.test(lastKnownShareCode) + + if (!isValidShareCode) { + return NextResponse.json({ error: 'expired-sharecode' }, { status: 400 }) + } + + try { + await prisma.user.update({ + where: { steamId }, + data: { + authCode: authCode && isValidAuthCode ? encrypt(authCode) : undefined, + lastKnownShareCode: lastKnownShareCode || undefined, + lastKnownShareCodeDate: lastKnownShareCode ? new Date() : undefined, + }, + }) + + return new NextResponse(null, { status: 204 }) + } catch (error) { + console.error('[PUT /api/cs2/sharecode]', error) + return NextResponse.json({ error: 'Fehler beim Speichern' }, { status: 500 }) } } diff --git a/src/app/api/matches/[id]/route.ts b/src/app/api/matches/[id]/route.ts index 1900393..2c109db 100644 --- a/src/app/api/matches/[id]/route.ts +++ b/src/app/api/matches/[id]/route.ts @@ -14,7 +14,7 @@ export async function GET(_: Request, { params }: Params) { try { const match = await prisma.match.findUnique({ - where: { matchId: BigInt(id) }, + where: { id }, include: { teamA: true, teamB: true, @@ -58,7 +58,7 @@ export async function PUT(req: NextRequest, { params }: Params) { const { title, description, matchDate, players } = body const match = await prisma.match.findUnique({ - where: { matchId: BigInt(id) }, + where: { id }, include: { teamA: { include: { leader: true } }, teamB: { include: { leader: true } }, @@ -94,7 +94,7 @@ export async function PUT(req: NextRequest, { params }: Params) { } try { - await prisma.matchPlayer.deleteMany({ where: { matchId: BigInt(id) } }) + await prisma.matchPlayer.deleteMany({ where: { id } }) await prisma.matchPlayer.createMany({ data: players.map((p: any) => ({ @@ -105,7 +105,7 @@ export async function PUT(req: NextRequest, { params }: Params) { }) const updated = await prisma.match.update({ - where: { matchId: BigInt(id) }, + where: { id }, data: { title, description, @@ -159,7 +159,7 @@ export async function DELETE(req: NextRequest, { params }: Params) { const { id } = params try { - await prisma.match.delete({ where: { matchId: BigInt(id) } }) + await prisma.match.delete({ where: { id } }) return NextResponse.json({ success: true }) } catch (err) { console.error(`DELETE /matches/${id} failed:`, err) diff --git a/src/app/api/matches/create/route.ts b/src/app/api/matches/create/route.ts index a302a9b..e2d8b3d 100644 --- a/src/app/api/matches/create/route.ts +++ b/src/app/api/matches/create/route.ts @@ -46,12 +46,12 @@ export async function POST (req: NextRequest) { /* 2. Spieler-Datensätze vorbereiten */ const playersData = [ ...teamA.activePlayers.map((steamId: string) => ({ - matchId: newMatch.matchId, + matchId: newMatch.id, steamId, teamId : teamAId })), ...teamB.activePlayers.map((steamId: string) => ({ - matchId: newMatch.matchId, + matchId: newMatch.id, steamId, teamId : teamBId })) diff --git a/src/app/api/team/create/route.ts b/src/app/api/team/create/route.ts index f8f7c07..b46eaaf 100644 --- a/src/app/api/team/create/route.ts +++ b/src/app/api/team/create/route.ts @@ -37,7 +37,7 @@ export async function POST(req: NextRequest) { await prisma.notification.create({ data: { - userId: leader, + userId: leader.steamId, title: 'Team erstellt', message: `Du hast erfolgreich das Team "${teamname}" erstellt.`, }, diff --git a/src/app/api/user/matches/route.ts b/src/app/api/user/matches/route.ts new file mode 100644 index 0000000..bb3acba --- /dev/null +++ b/src/app/api/user/matches/route.ts @@ -0,0 +1,58 @@ +// /app/api/user/matches/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/lib/auth'; +import { prisma } from '@/app/lib/prisma'; + +export async function GET(req: NextRequest) { + const session = await getServerSession(authOptions(req)); + const steamId = session?.user?.steamId; + + if (!steamId) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const matchPlayers = await prisma.matchPlayer.findMany({ + where: { steamId }, + include: { + match: { + select: { + matchDate: true, + map: true, + scoreA: true, + scoreB: true, + matchType: true, + rankUpdates: true, + }, + }, + stats: true, + }, + orderBy: { + match: { + matchDate: 'desc', + }, + }, + }); + + const data = matchPlayers.map((mp) => { + const isTeamA = mp.teamId === mp.match.teamAId; + const kills = mp.stats?.kills ?? 0; + const deaths = mp.stats?.deaths ?? 0; + const kd = deaths > 0 ? (kills / deaths).toFixed(2) : '∞'; + + return { + map: mp.match.map ?? 'Unknown', + date: mp.match.matchDate, + score: `${mp.match.scoreA ?? 0} : ${mp.match.scoreB ?? 0}`, + isTeamA, + rankNew: mp.stats?.rankNew ?? null, + rankOld: mp.stats?.rankOld ?? null, + rating: (mp.stats?.adr ?? 0).toFixed(2), // optional: hier besser eigener Rating-Alg. + kills, + deaths, + kd, + }; + }); + + return NextResponse.json(data); +} diff --git a/src/app/components/MatchesAdminManager.tsx b/src/app/components/MatchesAdminManager.tsx index 75163f3..d58227a 100644 --- a/src/app/components/MatchesAdminManager.tsx +++ b/src/app/components/MatchesAdminManager.tsx @@ -23,7 +23,7 @@ function getRoundedDate() { } function getTeamLogo(logo?: string | null) { - return logo ? `/assets/img/logos/${logo}` : '/default-logo.png' + return logo ? `/assets/img/logos/${logo}` : '/assets/img/logos/cs2.webp' } export default function MatchesAdminManager() { @@ -110,14 +110,14 @@ export default function MatchesAdminManager() {
Ungültiger Austauschcode
+ {showError && ( ++ Abgelaufener Austauschcode +
+ )} + {isSaved && !showError && ( ++ ✓ Gespeichert! +
)}