changes
99
package-lock.json
generated
@ -14,7 +14,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@preline/dropdown": "^3.0.1",
|
"@preline/dropdown": "^3.0.1",
|
||||||
"@preline/tooltip": "^3.0.0",
|
"@preline/tooltip": "^3.0.0",
|
||||||
"@prisma/client": "^6.7.0",
|
"@prisma/client": "^6.9.0",
|
||||||
"csgo-sharecode": "^3.1.2",
|
"csgo-sharecode": "^3.1.2",
|
||||||
"datatables.net": "^2.2.2",
|
"datatables.net": "^2.2.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.0",
|
"eslint-config-next": "15.3.0",
|
||||||
"prisma": "^6.7.0",
|
"prisma": "^6.9.0",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
@ -1522,9 +1522,9 @@
|
|||||||
"license": "Licensed under MIT and Preline UI Fair Use License"
|
"license": "Licensed under MIT and Preline UI Fair Use License"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/client": {
|
"node_modules/@prisma/client": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz",
|
||||||
"integrity": "sha512-+k61zZn1XHjbZul8q6TdQLpuI/cvyfil87zqK2zpreNIXyXtpUv3+H/oM69hcsFcZXaokHJIzPAt5Z8C8eK2QA==",
|
"integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1544,64 +1544,63 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/config": {
|
"node_modules/@prisma/config": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz",
|
||||||
"integrity": "sha512-di8QDdvSz7DLUi3OOcCHSwxRNeW7jtGRUD2+Z3SdNE3A+pPiNT8WgUJoUyOwJmUr5t+JA2W15P78C/N+8RXrOA==",
|
"integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": ">=0.12 <1",
|
"jiti": "2.4.2"
|
||||||
"esbuild-register": "3.6.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/debug": {
|
"node_modules/@prisma/debug": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz",
|
||||||
"integrity": "sha512-RabHn9emKoYFsv99RLxvfG2GHzWk2ZI1BuVzqYtmMSIcuGboHY5uFt3Q3boOREM9de6z5s3bQoyKeWnq8Fz22w==",
|
"integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines": {
|
"node_modules/@prisma/engines": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.9.0.tgz",
|
||||||
"integrity": "sha512-3wDMesnOxPrOsq++e5oKV9LmIiEazFTRFZrlULDQ8fxdub5w4NgRBoxtWbvXmj2nJVCnzuz6eFix3OhIqsZ1jw==",
|
"integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.7.0",
|
"@prisma/debug": "6.9.0",
|
||||||
"@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
|
"@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
||||||
"@prisma/fetch-engine": "6.7.0",
|
"@prisma/fetch-engine": "6.9.0",
|
||||||
"@prisma/get-platform": "6.7.0"
|
"@prisma/get-platform": "6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines-version": {
|
"node_modules/@prisma/engines-version": {
|
||||||
"version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
|
"version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz",
|
||||||
"integrity": "sha512-EvpOFEWf1KkJpDsBCrih0kg3HdHuaCnXmMn7XFPObpFTzagK1N0Q0FMnYPsEhvARfANP5Ok11QyoTIRA2hgJTA==",
|
"integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/fetch-engine": {
|
"node_modules/@prisma/fetch-engine": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz",
|
||||||
"integrity": "sha512-zLlAGnrkmioPKJR4Yf7NfW3hftcvqeNNEHleMZK9yX7RZSkhmxacAYyfGsCcqRt47jiZ7RKdgE0Wh2fWnm7WsQ==",
|
"integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.7.0",
|
"@prisma/debug": "6.9.0",
|
||||||
"@prisma/engines-version": "6.7.0-36.3cff47a7f5d65c3ea74883f1d736e41d68ce91ed",
|
"@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
||||||
"@prisma/get-platform": "6.7.0"
|
"@prisma/get-platform": "6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/get-platform": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz",
|
||||||
"integrity": "sha512-i9IH5lO4fQwnMLvQLYNdgVh9TK3PuWBfQd7QLk/YurnAIg+VeADcZDbmhAi4XBBDD+hDif9hrKyASu0hbjwabw==",
|
"integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.7.0"
|
"@prisma/debug": "6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
@ -3159,7 +3158,7 @@
|
|||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
@ -3477,7 +3476,7 @@
|
|||||||
"version": "0.25.3",
|
"version": "0.25.3",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
|
||||||
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
|
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -3514,19 +3513,6 @@
|
|||||||
"@esbuild/win32-x64": "0.25.3"
|
"@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": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
@ -4988,7 +4974,7 @@
|
|||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
|
||||||
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
"integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "lib/jiti-cli.mjs"
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
@ -5553,7 +5539,7 @@
|
|||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
@ -6223,15 +6209,15 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.9.0.tgz",
|
||||||
"integrity": "sha512-vArg+4UqnQ13CVhc2WUosemwh6hr6cr6FY2uzDvCIFwH8pu8BXVv38PktoMLVjtX7sbYThxbnZF5YiR8sN2clw==",
|
"integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "6.7.0",
|
"@prisma/config": "6.9.0",
|
||||||
"@prisma/engines": "6.7.0"
|
"@prisma/engines": "6.9.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"prisma": "build/index.js"
|
"prisma": "build/index.js"
|
||||||
@ -6239,9 +6225,6 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18"
|
"node": ">=18.18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "2.3.3"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": ">=5.1.0"
|
"typescript": ">=5.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@preline/dropdown": "^3.0.1",
|
"@preline/dropdown": "^3.0.1",
|
||||||
"@preline/tooltip": "^3.0.0",
|
"@preline/tooltip": "^3.0.0",
|
||||||
"@prisma/client": "^6.7.0",
|
"@prisma/client": "^6.9.0",
|
||||||
"csgo-sharecode": "^3.1.2",
|
"csgo-sharecode": "^3.1.2",
|
||||||
"datatables.net": "^2.2.2",
|
"datatables.net": "^2.2.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.3.0",
|
"eslint-config-next": "15.3.0",
|
||||||
"prisma": "^6.7.0",
|
"prisma": "^6.9.0",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
|
|||||||
@ -54,7 +54,7 @@ model Team {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Match {
|
model Match {
|
||||||
matchId BigInt @id @default(autoincrement())
|
id String @id @default(uuid())
|
||||||
teamAId String?
|
teamAId String?
|
||||||
teamBId String?
|
teamBId String?
|
||||||
matchDate DateTime
|
matchDate DateTime
|
||||||
@ -78,11 +78,11 @@ model Match {
|
|||||||
|
|
||||||
model MatchPlayer {
|
model MatchPlayer {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
matchId BigInt
|
matchId String
|
||||||
steamId String
|
steamId String
|
||||||
teamId 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])
|
user User @relation(fields: [steamId], references: [steamId])
|
||||||
team Team? @relation(fields: [teamId], references: [id])
|
team Team? @relation(fields: [teamId], references: [id])
|
||||||
stats MatchPlayerStats?
|
stats MatchPlayerStats?
|
||||||
@ -92,6 +92,7 @@ model MatchPlayer {
|
|||||||
@@unique([matchId, steamId])
|
@@unique([matchId, steamId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model MatchPlayerStats {
|
model MatchPlayerStats {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
matchPlayerId String @unique
|
matchPlayerId String @unique
|
||||||
@ -123,17 +124,18 @@ model MatchPlayerStats {
|
|||||||
|
|
||||||
model DemoFile {
|
model DemoFile {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
matchId BigInt @unique
|
matchId String @unique
|
||||||
steamId String
|
steamId String
|
||||||
fileName String @unique
|
fileName String @unique
|
||||||
filePath String
|
filePath String
|
||||||
parsed Boolean @default(false)
|
parsed Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
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])
|
user User @relation(fields: [steamId], references: [steamId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model Invitation {
|
model Invitation {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
@ -163,10 +165,11 @@ model CS2MatchRequest {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
steamId String
|
steamId String
|
||||||
matchId BigInt
|
matchId String
|
||||||
reservationId BigInt
|
reservationId BigInt
|
||||||
tvPort BigInt
|
tvPort BigInt
|
||||||
processed Boolean @default(false)
|
processed Boolean @default(false)
|
||||||
|
failed Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation("MatchRequests", fields: [userId], references: [steamId])
|
user User @relation("MatchRequests", fields: [userId], references: [steamId])
|
||||||
@ -174,11 +177,12 @@ model CS2MatchRequest {
|
|||||||
@@unique([steamId, matchId])
|
@@unique([steamId, matchId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
model PremierRankHistory {
|
model PremierRankHistory {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
steamId String
|
steamId String
|
||||||
matchId BigInt?
|
matchId String? // optionaler String
|
||||||
|
|
||||||
rankOld Int
|
rankOld Int
|
||||||
rankNew Int
|
rankNew Int
|
||||||
@ -187,5 +191,5 @@ model PremierRankHistory {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation("UserRankHistory", fields: [userId], references: [steamId])
|
user User @relation("UserRankHistory", fields: [userId], references: [steamId])
|
||||||
match Match? @relation("MatchRankHistory", fields: [matchId], references: [matchId])
|
match Match? @relation("MatchRankHistory", fields: [matchId], references: [id]) // 👈 id statt matchId
|
||||||
}
|
}
|
||||||
BIN
public/assets/img/logos/cs2.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/assets/img/maps/ar_baggage.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/assets/img/maps/ar_pool_day.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/img/maps/ar_shoots.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/assets/img/maps/cs_agency.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/img/maps/cs_italy.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/assets/img/maps/cs_office.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/assets/img/maps/de_ancient.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/assets/img/maps/de_anubis.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/img/maps/de_brewery.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/img/maps/de_dogtown.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/img/maps/de_dust2.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/assets/img/maps/de_grail.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/assets/img/maps/de_inferno.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/assets/img/maps/de_jura.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/assets/img/maps/de_mirage.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/assets/img/maps/de_nuke.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/assets/img/maps/de_overpass.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/assets/img/maps/de_train.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/assets/img/maps/de_vertigo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/assets/img/maps/lobby_mapveto.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
@ -1,36 +1,67 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { getServerSession } from 'next-auth'
|
import { prisma } from '@/app/lib/prisma';
|
||||||
import { authOptions } from '@/app/lib/auth'
|
import { decodeMatchShareCode } from 'csgo-sharecode';
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { decrypt } from '@/app/lib/crypto';
|
||||||
import { decrypt, encrypt } from '@/app/lib/crypto'
|
|
||||||
import { decodeMatchShareCode, MatchInformation } from 'csgo-sharecode';
|
|
||||||
|
|
||||||
function delay(ms: number) {
|
// Maximal 30 Tage gültig
|
||||||
return new Promise(resolve => setTimeout(resolve, ms))
|
const EXPIRY_DAYS = 30;
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const session = await getServerSession(authOptions(req))
|
const steamId = req.headers.get('x-steamid') ?? undefined;
|
||||||
let steamId = session?.user?.steamId ?? req.headers.get('x-steamid') ?? undefined;
|
|
||||||
|
|
||||||
if (!steamId) {
|
if (!steamId) {
|
||||||
return NextResponse.json({ valid: false, reason: 'Missing 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 {
|
try {
|
||||||
const user = await prisma.user.findUnique({
|
const url = `https://api.steampowered.com/ICSGOPlayers_730/GetNextMatchSharingCode/v1?key=${process.env.STEAM_API_KEY}&steamid=${steamId}&steamidkey=${authCode}&knowncode=${knownCode}`;
|
||||||
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 res = await fetch(url);
|
const res = await fetch(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@ -41,42 +72,29 @@ export async function GET(req: NextRequest) {
|
|||||||
return NextResponse.json({ valid: true, nextCode: null });
|
return NextResponse.json({ valid: true, nextCode: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchInfo extrahieren & speichern
|
|
||||||
const matchInfo = decodeMatchShareCode(nextCode);
|
const matchInfo = decodeMatchShareCode(nextCode);
|
||||||
await prisma.cS2MatchRequest.upsert({
|
await prisma.cS2MatchRequest.upsert({
|
||||||
where: {
|
where: {
|
||||||
steamId_matchId: {
|
steamId_matchId: {
|
||||||
steamId,
|
steamId,
|
||||||
matchId: matchInfo.matchId,
|
matchId: matchInfo.matchId.toString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
update: {},
|
update: {},
|
||||||
create: {
|
create: {
|
||||||
userId: steamId,
|
userId: steamId,
|
||||||
steamId: steamId,
|
steamId: steamId,
|
||||||
matchId: matchInfo.matchId,
|
matchId: matchInfo.matchId.toString(),
|
||||||
reservationId: matchInfo.reservationId,
|
reservationId: matchInfo.reservationId,
|
||||||
tvPort: matchInfo.tvPort,
|
tvPort: matchInfo.tvPort,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({ valid: true, nextCode });
|
||||||
valid: true,
|
|
||||||
nextCode,
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (err) {
|
} 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({
|
return NextResponse.json({
|
||||||
valid: false,
|
valid: false,
|
||||||
error: err instanceof Error ? err.message : String(err),
|
error: err instanceof Error ? err.message : String(err),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,40 +1,13 @@
|
|||||||
|
// /api/cs2/sharecode/route.ts
|
||||||
|
|
||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import { getServerSession } from 'next-auth'
|
import { getServerSession } from 'next-auth'
|
||||||
import { authOptions } from '@/app/lib/auth'
|
import { authOptions } from '@/app/lib/auth'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
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) {
|
// Maximal 30 Tage gültig
|
||||||
const session = await getServerSession(authOptions(req))
|
const EXPIRY_DAYS = 30
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function GET(req: NextRequest) {
|
export async function GET(req: NextRequest) {
|
||||||
const session = await getServerSession(authOptions(req))
|
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({
|
return NextResponse.json({
|
||||||
authCode: user?.authCode ? decrypt(user.authCode) : null,
|
authCode,
|
||||||
lastKnownShareCode: user?.lastKnownShareCode ?? null,
|
lastKnownShareCode,
|
||||||
lastKnownShareCodeDate: user?.lastKnownShareCodeDate ?? null,
|
lastKnownShareCodeDate,
|
||||||
|
reason,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Abrufen:', error)
|
console.error('[GET /api/cs2/sharecode]', error)
|
||||||
return NextResponse.json({ error: 'Fehler beim Abrufen der Codes' }, { status: 500 })
|
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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export async function GET(_: Request, { params }: Params) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const match = await prisma.match.findUnique({
|
const match = await prisma.match.findUnique({
|
||||||
where: { matchId: BigInt(id) },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
teamA: true,
|
teamA: true,
|
||||||
teamB: true,
|
teamB: true,
|
||||||
@ -58,7 +58,7 @@ export async function PUT(req: NextRequest, { params }: Params) {
|
|||||||
const { title, description, matchDate, players } = body
|
const { title, description, matchDate, players } = body
|
||||||
|
|
||||||
const match = await prisma.match.findUnique({
|
const match = await prisma.match.findUnique({
|
||||||
where: { matchId: BigInt(id) },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
teamA: { include: { leader: true } },
|
teamA: { include: { leader: true } },
|
||||||
teamB: { include: { leader: true } },
|
teamB: { include: { leader: true } },
|
||||||
@ -94,7 +94,7 @@ export async function PUT(req: NextRequest, { params }: Params) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.matchPlayer.deleteMany({ where: { matchId: BigInt(id) } })
|
await prisma.matchPlayer.deleteMany({ where: { id } })
|
||||||
|
|
||||||
await prisma.matchPlayer.createMany({
|
await prisma.matchPlayer.createMany({
|
||||||
data: players.map((p: any) => ({
|
data: players.map((p: any) => ({
|
||||||
@ -105,7 +105,7 @@ export async function PUT(req: NextRequest, { params }: Params) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const updated = await prisma.match.update({
|
const updated = await prisma.match.update({
|
||||||
where: { matchId: BigInt(id) },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -159,7 +159,7 @@ export async function DELETE(req: NextRequest, { params }: Params) {
|
|||||||
const { id } = params
|
const { id } = params
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await prisma.match.delete({ where: { matchId: BigInt(id) } })
|
await prisma.match.delete({ where: { id } })
|
||||||
return NextResponse.json({ success: true })
|
return NextResponse.json({ success: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`DELETE /matches/${id} failed:`, err)
|
console.error(`DELETE /matches/${id} failed:`, err)
|
||||||
|
|||||||
@ -46,12 +46,12 @@ export async function POST (req: NextRequest) {
|
|||||||
/* 2. Spieler-Datensätze vorbereiten */
|
/* 2. Spieler-Datensätze vorbereiten */
|
||||||
const playersData = [
|
const playersData = [
|
||||||
...teamA.activePlayers.map((steamId: string) => ({
|
...teamA.activePlayers.map((steamId: string) => ({
|
||||||
matchId: newMatch.matchId,
|
matchId: newMatch.id,
|
||||||
steamId,
|
steamId,
|
||||||
teamId : teamAId
|
teamId : teamAId
|
||||||
})),
|
})),
|
||||||
...teamB.activePlayers.map((steamId: string) => ({
|
...teamB.activePlayers.map((steamId: string) => ({
|
||||||
matchId: newMatch.matchId,
|
matchId: newMatch.id,
|
||||||
steamId,
|
steamId,
|
||||||
teamId : teamBId
|
teamId : teamBId
|
||||||
}))
|
}))
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
await prisma.notification.create({
|
await prisma.notification.create({
|
||||||
data: {
|
data: {
|
||||||
userId: leader,
|
userId: leader.steamId,
|
||||||
title: 'Team erstellt',
|
title: 'Team erstellt',
|
||||||
message: `Du hast erfolgreich das Team "${teamname}" erstellt.`,
|
message: `Du hast erfolgreich das Team "${teamname}" erstellt.`,
|
||||||
},
|
},
|
||||||
|
|||||||
58
src/app/api/user/matches/route.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
@ -23,7 +23,7 @@ function getRoundedDate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTeamLogo(logo?: string | null) {
|
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() {
|
export default function MatchesAdminManager() {
|
||||||
@ -110,14 +110,14 @@ export default function MatchesAdminManager() {
|
|||||||
<div className="flex items-center justify-between text-center">
|
<div className="flex items-center justify-between text-center">
|
||||||
<div className="flex flex-col items-center w-1/4">
|
<div className="flex flex-col items-center w-1/4">
|
||||||
<Image
|
<Image
|
||||||
src={getTeamLogo(match.teamA.logo)}
|
src={getTeamLogo(match.teamA?.logo)}
|
||||||
alt={match.teamA.teamname}
|
alt={match.teamA?.teamname || 'Team A'}
|
||||||
width={64}
|
width={64}
|
||||||
height={64}
|
height={64}
|
||||||
className="rounded-full border object-cover bg-white"
|
className="rounded-full border object-cover bg-white"
|
||||||
/>
|
/>
|
||||||
<span className="mt-2 text-sm text-gray-700 dark:text-gray-200">
|
<span className="mt-2 text-sm text-gray-700 dark:text-gray-200">
|
||||||
{match.teamA.teamname}
|
{match.teamA?.teamname || 'Team A'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-600 dark:text-gray-300 w-1/2">
|
<div className="text-sm text-gray-600 dark:text-gray-300 w-1/2">
|
||||||
@ -129,14 +129,14 @@ export default function MatchesAdminManager() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center w-1/4">
|
<div className="flex flex-col items-center w-1/4">
|
||||||
<Image
|
<Image
|
||||||
src={getTeamLogo(match.teamB.logo)}
|
src={getTeamLogo(match.teamB?.logo)}
|
||||||
alt={match.teamB.teamname}
|
alt={match.teamB?.teamname || 'Team B'}
|
||||||
width={64}
|
width={64}
|
||||||
height={64}
|
height={64}
|
||||||
className="rounded-full border object-cover bg-white"
|
className="rounded-full border object-cover bg-white"
|
||||||
/>
|
/>
|
||||||
<span className="mt-2 text-sm text-gray-700 dark:text-gray-200">
|
<span className="mt-2 text-sm text-gray-700 dark:text-gray-200">
|
||||||
{match.teamB.teamname}
|
{match.teamB?.teamname || 'Team B'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -72,7 +72,8 @@ export default function NotificationCenter() {
|
|||||||
'team-left',
|
'team-left',
|
||||||
'team-member-left',
|
'team-member-left',
|
||||||
'team-leader-changed',
|
'team-leader-changed',
|
||||||
'team-join-request'
|
'team-join-request',
|
||||||
|
'expired-sharecode'
|
||||||
].includes(data.type)
|
].includes(data.type)
|
||||||
|
|
||||||
if (!isNotificationType) return
|
if (!isNotificationType) return
|
||||||
|
|||||||
@ -12,11 +12,11 @@ function TableWrapper({ children }: TableProps) {
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="-m-1.5 overflow-x-auto">
|
<div className="-m-1.5 overflow-x-auto">
|
||||||
<div className="p-1.5 min-w-full inline-block align-middle">
|
<div className="p-1.5 min-w-full inline-block align-middle">
|
||||||
<div className="border border-t-0 border-gray-200 overflow-hidden dark:border-neutral-700">
|
<div className="overflow-hidden">
|
||||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
|
<table className="min-w-full divide-y divide-gray-200 dark:divide-neutral-700">
|
||||||
{children}
|
{children}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -24,7 +24,7 @@ function TableWrapper({ children }: TableProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Head({ children }: { children: ReactNode }) {
|
function Head({ children }: { children: ReactNode }) {
|
||||||
return <thead className="bg-gray-50 dark:bg-neutral-700">{children}</thead>
|
return <thead className="px-6 py-3 text-start text-xs font-medium text-gray-500 uppercase dark:text-neutral-500">{children}</thead>
|
||||||
}
|
}
|
||||||
|
|
||||||
function Body({ children }: { children: ReactNode }) {
|
function Body({ children }: { children: ReactNode }) {
|
||||||
@ -32,15 +32,16 @@ function Body({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Row({
|
function Row({
|
||||||
children,
|
children,
|
||||||
hoverable = false,
|
hoverable = false,
|
||||||
}: {
|
...rest
|
||||||
children: ReactNode
|
}: {
|
||||||
hoverable?: boolean
|
children: ReactNode
|
||||||
}) {
|
hoverable?: boolean
|
||||||
const className = hoverable ? 'hover:bg-gray-100 dark:hover:bg-neutral-700' : ''
|
} & React.HTMLAttributes<HTMLTableRowElement>) {
|
||||||
return <tr className={className}>{children}</tr>
|
const className = `${hoverable ? 'hover:bg-gray-100 dark:hover:bg-neutral-700' : ''} ${rest.className ?? ''}`
|
||||||
}
|
return <tr {...rest} className={className}>{children}</tr>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function Cell({
|
function Cell({
|
||||||
|
|||||||
91
src/app/components/UserMatchesTable.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Table from './Table';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { mapNameMap } from '../lib/mapNameMap';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
interface Match {
|
||||||
|
id: string;
|
||||||
|
map: string;
|
||||||
|
date: string;
|
||||||
|
score: string;
|
||||||
|
rating: string;
|
||||||
|
kills: number;
|
||||||
|
deaths: number;
|
||||||
|
kd: string;
|
||||||
|
rankNew: number | null;
|
||||||
|
rankOld: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserMatchesTable() {
|
||||||
|
const [matches, setMatches] = useState<Match[]>([]);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch('/api/user/matches')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then(setMatches)
|
||||||
|
.catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<Table.Head>
|
||||||
|
<Table.Row>
|
||||||
|
<Table.Cell as="th">Map</Table.Cell>
|
||||||
|
<Table.Cell as="th">Date</Table.Cell>
|
||||||
|
<Table.Cell as="th">Score</Table.Cell>
|
||||||
|
<Table.Cell as="th">Rank</Table.Cell>
|
||||||
|
<Table.Cell as="th">Kills</Table.Cell>
|
||||||
|
<Table.Cell as="th">Deaths</Table.Cell>
|
||||||
|
<Table.Cell as="th">K/D</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
</Table.Head>
|
||||||
|
<Table.Body>
|
||||||
|
{matches.map((m) => {
|
||||||
|
const mapInfo = mapNameMap[m.map] ?? mapNameMap['lobby_mapveto'];
|
||||||
|
return (
|
||||||
|
<Table.Row
|
||||||
|
key={m.id}
|
||||||
|
hoverable
|
||||||
|
onClick={() => router.push(`/matches-details/${m.id}`)}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<Table.Cell>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={`/assets/img/maps/${m.map}.png`}
|
||||||
|
alt={mapInfo.name}
|
||||||
|
className="w-5 h-5 rounded"
|
||||||
|
/>
|
||||||
|
{mapInfo.name}
|
||||||
|
</div>
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>{new Date(m.date).toLocaleString()}</Table.Cell>
|
||||||
|
<Table.Cell>{m.score}</Table.Cell>
|
||||||
|
<Table.Cell>
|
||||||
|
{m.rankNew !== null ? (
|
||||||
|
m.rankOld !== null ? (
|
||||||
|
<span className={m.rankNew > m.rankOld ? 'text-green-500' : m.rankNew < m.rankOld ? 'text-red-500' : ''}>
|
||||||
|
{m.rankNew > m.rankOld ? '+' : ''}
|
||||||
|
{m.rankNew - m.rankOld}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
m.rankNew
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
'—'
|
||||||
|
)}
|
||||||
|
</Table.Cell>
|
||||||
|
<Table.Cell>{m.kills}</Table.Cell>
|
||||||
|
<Table.Cell>{m.deaths}</Table.Cell>
|
||||||
|
<Table.Cell>{m.kd}</Table.Cell>
|
||||||
|
</Table.Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import Popover from '../../Popover'
|
import Popover from '../../Popover'
|
||||||
@ -8,17 +8,24 @@ import LatestKnownCodeSettings from './LatestKnownCodeSettings'
|
|||||||
|
|
||||||
export default function AuthCodeSettings() {
|
export default function AuthCodeSettings() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const [userId, setUserId] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const [authCode, setAuthCode] = useState('')
|
const [authCode, setAuthCode] = useState('')
|
||||||
const [authCodeValid, setAuthCodeValid] = useState(false)
|
const [authCodeValid, setAuthCodeValid] = useState(false)
|
||||||
|
|
||||||
const [lastKnownShareCode, setLastKnownShareCode] = useState('')
|
const [lastKnownShareCode, setLastKnownShareCode] = useState('')
|
||||||
const [lastKnownShareCodeValid, setLastKnownShareCodeValid] = useState(false)
|
const [lastKnownShareCodeDate, setLastKnownShareCodeDate] = useState<Date | null>(null)
|
||||||
|
const [shareCodeManuallySet, setShareCodeManuallySet] = useState(false)
|
||||||
|
const [shareCodeSaved, setShareCodeSaved] = useState(false)
|
||||||
|
|
||||||
const [touched, setTouched] = useState(false)
|
const [touched, setTouched] = useState(false)
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
|
const shareCodeExpired = useMemo(() => {
|
||||||
|
if (!lastKnownShareCodeDate) return false
|
||||||
|
const daysSince = (Date.now() - new Date(lastKnownShareCodeDate).getTime()) / (1000 * 60 * 60 * 24)
|
||||||
|
return daysSince > 30
|
||||||
|
}, [lastKnownShareCodeDate])
|
||||||
|
|
||||||
const formatAuthCode = (value: string) => {
|
const formatAuthCode = (value: string) => {
|
||||||
const raw = value.replace(/[^A-Za-z0-9]/g, '').toUpperCase()
|
const raw = value.replace(/[^A-Za-z0-9]/g, '').toUpperCase()
|
||||||
const part1 = raw.slice(0, 4)
|
const part1 = raw.slice(0, 4)
|
||||||
@ -37,7 +44,7 @@ export default function AuthCodeSettings() {
|
|||||||
if (!validateAuthCode(updatedAuthCode) || !validateShareCode(updatedKnownCode)) return
|
if (!validateAuthCode(updatedAuthCode) || !validateShareCode(updatedKnownCode)) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch('/api/cs2/sharecode', {
|
const res = await fetch('/api/cs2/sharecode', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -45,11 +52,26 @@ export default function AuthCodeSettings() {
|
|||||||
lastKnownShareCode: updatedKnownCode,
|
lastKnownShareCode: updatedKnownCode,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
setLastKnownShareCodeDate(new Date())
|
||||||
|
setShareCodeSaved(true)
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fehler beim Speichern der Codes:', err)
|
console.error('Fehler beim Speichern der Codes:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSetShareCode = (val: string) => {
|
||||||
|
setLastKnownShareCode(val)
|
||||||
|
setShareCodeManuallySet(true)
|
||||||
|
setShareCodeSaved(false)
|
||||||
|
|
||||||
|
if (validateShareCode(val) && authCodeValid) {
|
||||||
|
saveCodes(authCode, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleAuthCodeChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleAuthCodeChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const formatted = formatAuthCode(e.target.value)
|
const formatted = formatAuthCode(e.target.value)
|
||||||
setAuthCode(formatted)
|
setAuthCode(formatted)
|
||||||
@ -63,10 +85,6 @@ export default function AuthCodeSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (session?.user?.id) setUserId(session.user.id)
|
|
||||||
}, [session])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCodes = async () => {
|
const fetchCodes = async () => {
|
||||||
try {
|
try {
|
||||||
@ -78,9 +96,12 @@ export default function AuthCodeSettings() {
|
|||||||
setAuthCodeValid(validateAuthCode(data.authCode))
|
setAuthCodeValid(validateAuthCode(data.authCode))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.lastKnownShareCode) {
|
if (data?.lastKnownShareCode !== undefined) {
|
||||||
setLastKnownShareCode(data.lastKnownShareCode)
|
setLastKnownShareCode(data.lastKnownShareCode)
|
||||||
setLastKnownShareCodeValid(validateShareCode(data.lastKnownShareCode))
|
}
|
||||||
|
|
||||||
|
if (data?.lastKnownShareCodeDate) {
|
||||||
|
setLastKnownShareCodeDate(new Date(data.lastKnownShareCodeDate))
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
@ -92,6 +113,10 @@ export default function AuthCodeSettings() {
|
|||||||
fetchCodes()
|
fetchCodes()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const showShareCodeInput = !isLoading && (
|
||||||
|
!lastKnownShareCode || shareCodeExpired || shareCodeManuallySet
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="py-6 sm:py-8 space-y-5 border-t border-gray-200 dark:border-neutral-700">
|
<div className="py-6 sm:py-8 space-y-5 border-t border-gray-200 dark:border-neutral-700">
|
||||||
<div className="grid sm:grid-cols-12 gap-y-1.5 sm:gap-y-0 sm:gap-x-5">
|
<div className="grid sm:grid-cols-12 gap-y-1.5 sm:gap-y-0 sm:gap-x-5">
|
||||||
@ -130,8 +155,7 @@ export default function AuthCodeSettings() {
|
|||||||
onBlur={() => setTouched(true)}
|
onBlur={() => setTouched(true)}
|
||||||
className={`border py-2.5 sm:py-3 px-4 block w-full rounded-lg sm:text-sm
|
className={`border py-2.5 sm:py-3 px-4 block w-full rounded-lg sm:text-sm
|
||||||
dark:bg-neutral-800 dark:text-neutral-200 dark:border-neutral-700
|
dark:bg-neutral-800 dark:text-neutral-200 dark:border-neutral-700
|
||||||
${touched ? (authCodeValid ? 'border-teal-500 focus:border-teal-500 focus:ring-teal-500' : 'border-red-500 focus:border-red-500 focus:ring-red-500') : 'border-gray-200'}
|
${touched ? (authCodeValid ? 'border-teal-500 focus:border-teal-500 focus:ring-teal-500' : 'border-red-500 focus:border-red-500 focus:ring-red-500') : 'border-gray-200'}`}
|
||||||
`}
|
|
||||||
placeholder="XXXX-XXXXX-XXXX"
|
placeholder="XXXX-XXXXX-XXXX"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -154,17 +178,12 @@ export default function AuthCodeSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isLoading && (!lastKnownShareCodeValid || !authCodeValid) && (
|
{showShareCodeInput && (
|
||||||
<LatestKnownCodeSettings
|
<LatestKnownCodeSettings
|
||||||
lastKnownShareCode={lastKnownShareCode}
|
lastKnownShareCode={lastKnownShareCode}
|
||||||
setLastKnownShareCode={(val: string) => {
|
setLastKnownShareCode={handleSetShareCode}
|
||||||
setLastKnownShareCode(val)
|
isInvalid={shareCodeExpired}
|
||||||
const valid = validateShareCode(val)
|
isSaved={shareCodeSaved}
|
||||||
setLastKnownShareCodeValid(valid)
|
|
||||||
if (valid && authCodeValid) {
|
|
||||||
saveCodes(authCode, val)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,9 +6,16 @@ import Popover from '../../Popover'
|
|||||||
interface Props {
|
interface Props {
|
||||||
lastKnownShareCode: string
|
lastKnownShareCode: string
|
||||||
setLastKnownShareCode: (value: string) => void
|
setLastKnownShareCode: (value: string) => void
|
||||||
|
isInvalid?: boolean
|
||||||
|
isSaved?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LatestKnownCodeSettings({ lastKnownShareCode, setLastKnownShareCode }: Props) {
|
export default function LatestKnownCodeSettings({
|
||||||
|
lastKnownShareCode,
|
||||||
|
setLastKnownShareCode,
|
||||||
|
isInvalid = false,
|
||||||
|
isSaved = false,
|
||||||
|
}: Props) {
|
||||||
const formatLastKnownShareCode = (value: string) => {
|
const formatLastKnownShareCode = (value: string) => {
|
||||||
const raw = value.replace(/[^a-zA-Z0-9]/g, '')
|
const raw = value.replace(/[^a-zA-Z0-9]/g, '')
|
||||||
const part0 = raw.slice(0, 4)
|
const part0 = raw.slice(0, 4)
|
||||||
@ -29,6 +36,7 @@ export default function LatestKnownCodeSettings({ lastKnownShareCode, setLastKno
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isValid = validate(lastKnownShareCode)
|
const isValid = validate(lastKnownShareCode)
|
||||||
|
const showError = isInvalid || !isValid
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid sm:grid-cols-12 gap-y-1.5 sm:gap-y-0 sm:gap-x-5">
|
<div className="grid sm:grid-cols-12 gap-y-1.5 sm:gap-y-0 sm:gap-x-5">
|
||||||
@ -65,20 +73,28 @@ export default function LatestKnownCodeSettings({ lastKnownShareCode, setLastKno
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className={`border py-2.5 sm:py-3 px-4 block w-full rounded-lg sm:text-sm
|
className={`border py-2.5 sm:py-3 px-4 block w-full rounded-lg sm:text-sm
|
||||||
dark:bg-neutral-800 dark:text-neutral-200 dark:border-neutral-700
|
dark:bg-neutral-800 dark:text-neutral-200 dark:border-neutral-700
|
||||||
${isValid ? 'border-teal-500 focus:border-teal-500 focus:ring-teal-500' : 'border-red-500 focus:border-red-500 focus:ring-red-500'}
|
${showError
|
||||||
`}
|
? 'border-red-500 focus:border-red-500 focus:ring-red-500'
|
||||||
|
: 'border-teal-500 focus:border-teal-500 focus:ring-teal-500'}`}
|
||||||
placeholder="CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
|
placeholder="CSGO-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{isValid && (
|
{!showError && (
|
||||||
<div className="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none">
|
<div className="absolute inset-y-0 end-0 flex items-center pe-3 pointer-events-none">
|
||||||
<svg className="shrink-0 size-4 text-teal-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg className="shrink-0 size-4 text-teal-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
<polyline points="20 6 9 17 4 12" />
|
<polyline points="20 6 9 17 4 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isValid && (
|
{showError && (
|
||||||
<p className="text-sm text-red-600 mt-2">Ungültiger Austauschcode</p>
|
<p className="text-sm text-red-600 mt-2">
|
||||||
|
Abgelaufener Austauschcode
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{isSaved && !showError && (
|
||||||
|
<p className="text-sm text-teal-600 mt-2">
|
||||||
|
✓ Gespeichert!
|
||||||
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
24
src/app/lib/mapNameMap.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// src/lib/mapNameMap.ts
|
||||||
|
|
||||||
|
export const mapNameMap: Record<string, { name: string }> = {
|
||||||
|
de_train: { name: 'Train' },
|
||||||
|
ar_baggage: { name: 'Baggage' },
|
||||||
|
ar_pool_day: { name: 'Pool Day' },
|
||||||
|
ar_shoots: { name: 'Shoots' },
|
||||||
|
cs_agency: { name: 'Agency' },
|
||||||
|
cs_italy: { name: 'Italy' },
|
||||||
|
cs_office: { name: 'Office' },
|
||||||
|
de_ancient: { name: 'Ancient' },
|
||||||
|
de_anubis: { name: 'Anubis' },
|
||||||
|
de_brewery: { name: 'Brewery' },
|
||||||
|
de_dogtown: { name: 'Dogtown' },
|
||||||
|
de_dust2: { name: 'Dust 2' },
|
||||||
|
de_grail: { name: 'Grail' },
|
||||||
|
de_inferno: { name: 'Inferno' },
|
||||||
|
de_jura: { name: 'Jura' },
|
||||||
|
de_mirage: { name: 'Mirage' },
|
||||||
|
de_nuke: { name: 'Nuke' },
|
||||||
|
de_overpass: { name: 'Overpass' },
|
||||||
|
de_vertigo: { name: 'Vertigo' },
|
||||||
|
lobby_mapveto: { name: 'Pick/Ban' },
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const host = '10.0.1.25' // oder deine IP wie '10.0.1.25'
|
const host = 'localhost' // oder deine IP wie '10.0.1.25'
|
||||||
|
|
||||||
export async function sendServerWebSocketMessage(message: any) {
|
export async function sendServerWebSocketMessage(message: any) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export const useWS = create<WSState>((set, get) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const ws = new WebSocket(`ws://10.0.1.25:3001?steamId=${steamId}`)
|
const ws = new WebSocket(`ws://localhost:3001?steamId=${steamId}`)
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
set({ socket: ws, isConnected: true })
|
set({ socket: ws, isConnected: true })
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import Card from '@/app/components/Card'
|
|||||||
import CreateTeamButton from '@/app/components/CreateTeamButton'
|
import CreateTeamButton from '@/app/components/CreateTeamButton'
|
||||||
import TeamCardComponent from '@/app/components/TeamCardComponent'
|
import TeamCardComponent from '@/app/components/TeamCardComponent'
|
||||||
import AccountSettings from '@/app/components/settings/AccountSettings'
|
import AccountSettings from '@/app/components/settings/AccountSettings'
|
||||||
|
import UserMatchesTable from '@/app/components/UserMatchesTable'
|
||||||
|
|
||||||
export default function Page({ params }: { params: Promise<{ tab: string }> }) {
|
export default function Page({ params }: { params: Promise<{ tab: string }> }) {
|
||||||
const { tab } = use(params)
|
const { tab } = use(params)
|
||||||
@ -31,7 +32,7 @@ export default function Page({ params }: { params: Promise<{ tab: string }> }) {
|
|||||||
case 'matches':
|
case 'matches':
|
||||||
return (
|
return (
|
||||||
<Card maxWidth='auto'>
|
<Card maxWidth='auto'>
|
||||||
<TeamCardComponent />
|
<UserMatchesTable />
|
||||||
</Card>
|
</Card>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.7.0
|
* Prisma Client JS version: 6.9.0
|
||||||
* Query Engine version: 3cff47a7f5d65c3ea74883f1d736e41d68ce91ed
|
* Query Engine version: 81e4af48011447c3cc503a190e86995b66d2a28e
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.7.0",
|
client: "6.9.0",
|
||||||
engine: "3cff47a7f5d65c3ea74883f1d736e41d68ce91ed"
|
engine: "81e4af48011447c3cc503a190e86995b66d2a28e"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
@ -145,7 +145,7 @@ exports.Prisma.TeamScalarFieldEnum = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.Prisma.MatchScalarFieldEnum = {
|
exports.Prisma.MatchScalarFieldEnum = {
|
||||||
matchId: 'matchId',
|
id: 'id',
|
||||||
teamAId: 'teamAId',
|
teamAId: 'teamAId',
|
||||||
teamBId: 'teamBId',
|
teamBId: 'teamBId',
|
||||||
matchDate: 'matchDate',
|
matchDate: 'matchDate',
|
||||||
@ -232,6 +232,7 @@ exports.Prisma.CS2MatchRequestScalarFieldEnum = {
|
|||||||
reservationId: 'reservationId',
|
reservationId: 'reservationId',
|
||||||
tvPort: 'tvPort',
|
tvPort: 'tvPort',
|
||||||
processed: 'processed',
|
processed: 'processed',
|
||||||
|
failed: 'failed',
|
||||||
createdAt: 'createdAt'
|
createdAt: 'createdAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
638
src/generated/prisma/index.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-e738cc370bf095ce801f97ff86cc5e77ac9a34cd0d0b04f968607628b9755dee",
|
"name": "prisma-client-9a8c37bb270b506ae34f7883dcf8d6883cab59123439f5237b94576b020b37c3",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "index-browser.js",
|
"browser": "index-browser.js",
|
||||||
@ -97,11 +97,17 @@
|
|||||||
"import": "./runtime/binary.mjs",
|
"import": "./runtime/binary.mjs",
|
||||||
"default": "./runtime/binary.mjs"
|
"default": "./runtime/binary.mjs"
|
||||||
},
|
},
|
||||||
"./runtime/wasm": {
|
"./runtime/wasm-engine-edge": {
|
||||||
"types": "./runtime/wasm.d.ts",
|
"types": "./runtime/wasm-engine-edge.d.ts",
|
||||||
"require": "./runtime/wasm.js",
|
"require": "./runtime/wasm-engine-edge.js",
|
||||||
"import": "./runtime/wasm.mjs",
|
"import": "./runtime/wasm-engine-edge.mjs",
|
||||||
"default": "./runtime/wasm.mjs"
|
"default": "./runtime/wasm-engine-edge.mjs"
|
||||||
|
},
|
||||||
|
"./runtime/wasm-compiler-edge": {
|
||||||
|
"types": "./runtime/wasm-compiler-edge.d.ts",
|
||||||
|
"require": "./runtime/wasm-compiler-edge.js",
|
||||||
|
"import": "./runtime/wasm-compiler-edge.mjs",
|
||||||
|
"default": "./runtime/wasm-compiler-edge.mjs"
|
||||||
},
|
},
|
||||||
"./runtime/edge": {
|
"./runtime/edge": {
|
||||||
"types": "./runtime/edge.d.ts",
|
"types": "./runtime/edge.d.ts",
|
||||||
@ -135,6 +141,6 @@
|
|||||||
},
|
},
|
||||||
"./*": "./*"
|
"./*": "./*"
|
||||||
},
|
},
|
||||||
"version": "6.7.0",
|
"version": "6.9.0",
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
||||||
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp10484
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp10888
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp11008
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp11196
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp12472
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp20876
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp20904
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp21956
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp23172
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp25024
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp25168
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp26260
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp28380
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp28628
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp28780
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp28808
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp29524
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp4248
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp5944
Normal file
42
src/generated/prisma/runtime/library.d.ts
vendored
@ -73,7 +73,7 @@ export declare type Args_3<T, F extends Operation> = Args<T, F>;
|
|||||||
* Query arguments marked with this type are sanitized before being sent to the database.
|
* Query arguments marked with this type are sanitized before being sent to the database.
|
||||||
* Notice while a query argument may be `null`, `ArgType` is guaranteed to be defined.
|
* Notice while a query argument may be `null`, `ArgType` is guaranteed to be defined.
|
||||||
*/
|
*/
|
||||||
declare type ArgType = 'Int32' | 'Int64' | 'Float' | 'Double' | 'Text' | 'Enum' | 'EnumArray' | 'Bytes' | 'Boolean' | 'Char' | 'Array' | 'Numeric' | 'Json' | 'Xml' | 'Uuid' | 'DateTime' | 'Date' | 'Time';
|
declare type ArgType = 'Int32' | 'Int64' | 'Float' | 'Double' | 'Text' | 'Enum' | 'EnumArray' | 'Bytes' | 'Boolean' | 'Char' | 'Array' | 'Numeric' | 'Json' | 'Xml' | 'Uuid' | 'DateTime' | 'Date' | 'Time' | 'Unknown';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attributes is a map from string to attribute values.
|
* Attributes is a map from string to attribute values.
|
||||||
@ -98,7 +98,7 @@ export declare type BaseDMMF = {
|
|||||||
declare type BatchArgs = {
|
declare type BatchArgs = {
|
||||||
queries: BatchQuery[];
|
queries: BatchQuery[];
|
||||||
transaction?: {
|
transaction?: {
|
||||||
isolationLevel?: IsolationLevel;
|
isolationLevel?: IsolationLevel_2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ declare type BatchQueryOptionsCbArgs = {
|
|||||||
declare type BatchResponse = MultiBatchResponse | CompactedBatchResponse;
|
declare type BatchResponse = MultiBatchResponse | CompactedBatchResponse;
|
||||||
|
|
||||||
declare type BatchTransactionOptions = {
|
declare type BatchTransactionOptions = {
|
||||||
isolationLevel?: IsolationLevel;
|
isolationLevel?: Transaction_2.IsolationLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare interface BinaryTargetsEnvValue {
|
declare interface BinaryTargetsEnvValue {
|
||||||
@ -1475,7 +1475,7 @@ export declare type GetAggregateResult<P extends OperationPayload, A> = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
declare function getBatchRequestPayload(batch: JsonQuery[], transaction?: TransactionOptions_3<unknown>): QueryEngineBatchRequest;
|
declare function getBatchRequestPayload(batch: JsonQuery[], transaction?: TransactionOptions_2<unknown>): QueryEngineBatchRequest;
|
||||||
|
|
||||||
export declare type GetBatchResult = {
|
export declare type GetBatchResult = {
|
||||||
count: number;
|
count: number;
|
||||||
@ -1638,7 +1638,7 @@ export declare function getPrismaClient(config: GetPrismaClientConfig): {
|
|||||||
*/
|
*/
|
||||||
_transactionWithCallback({ callback, options, }: {
|
_transactionWithCallback({ callback, options, }: {
|
||||||
callback: (client: Client) => Promise<unknown>;
|
callback: (client: Client) => Promise<unknown>;
|
||||||
options?: TransactionOptions_2;
|
options?: Options;
|
||||||
}): Promise<unknown>;
|
}): Promise<unknown>;
|
||||||
_createItxClient(transaction: PrismaPromiseInteractiveTransaction): Client;
|
_createItxClient(transaction: PrismaPromiseInteractiveTransaction): Client;
|
||||||
/**
|
/**
|
||||||
@ -1963,6 +1963,8 @@ declare type InternalRequestParams = {
|
|||||||
|
|
||||||
declare type IsolationLevel = 'READ UNCOMMITTED' | 'READ COMMITTED' | 'REPEATABLE READ' | 'SNAPSHOT' | 'SERIALIZABLE';
|
declare type IsolationLevel = 'READ UNCOMMITTED' | 'READ COMMITTED' | 'REPEATABLE READ' | 'SNAPSHOT' | 'SERIALIZABLE';
|
||||||
|
|
||||||
|
declare type IsolationLevel_2 = 'ReadUncommitted' | 'ReadCommitted' | 'RepeatableRead' | 'Snapshot' | 'Serializable';
|
||||||
|
|
||||||
declare function isSkip(value: unknown): value is Skip;
|
declare function isSkip(value: unknown): value is Skip;
|
||||||
|
|
||||||
export declare function isTypedSql(value: unknown): value is UnknownTypedSql;
|
export declare function isTypedSql(value: unknown): value is UnknownTypedSql;
|
||||||
@ -2007,7 +2009,7 @@ export declare interface JsonArray extends Array<JsonValue> {
|
|||||||
export declare type JsonBatchQuery = {
|
export declare type JsonBatchQuery = {
|
||||||
batch: JsonQuery[];
|
batch: JsonQuery[];
|
||||||
transaction?: {
|
transaction?: {
|
||||||
isolationLevel?: IsolationLevel;
|
isolationLevel?: IsolationLevel_2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2422,6 +2424,15 @@ export declare type OptionalKeys<O> = {
|
|||||||
}[keyof O];
|
}[keyof O];
|
||||||
|
|
||||||
declare type Options = {
|
declare type Options = {
|
||||||
|
/** Timeout for starting the transaction */
|
||||||
|
maxWait?: number;
|
||||||
|
/** Timeout for the transaction body */
|
||||||
|
timeout?: number;
|
||||||
|
/** Transaction isolation level */
|
||||||
|
isolationLevel?: IsolationLevel_2;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type Options_2 = {
|
||||||
clientVersion: string;
|
clientVersion: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2570,7 +2581,7 @@ export declare class PrismaClientUnknownRequestError extends Error implements Er
|
|||||||
export declare class PrismaClientValidationError extends Error {
|
export declare class PrismaClientValidationError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
clientVersion: string;
|
clientVersion: string;
|
||||||
constructor(message: string, { clientVersion }: Options);
|
constructor(message: string, { clientVersion }: Options_2);
|
||||||
get [Symbol.toStringTag](): string;
|
get [Symbol.toStringTag](): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2622,7 +2633,7 @@ declare interface PrismaPromise_2<TResult, TSpec extends PrismaOperationSpec<unk
|
|||||||
declare type PrismaPromiseBatchTransaction = {
|
declare type PrismaPromiseBatchTransaction = {
|
||||||
kind: 'batch';
|
kind: 'batch';
|
||||||
id: number;
|
id: number;
|
||||||
isolationLevel?: IsolationLevel;
|
isolationLevel?: IsolationLevel_2;
|
||||||
index: number;
|
index: number;
|
||||||
lock: PromiseLike<void>;
|
lock: PromiseLike<void>;
|
||||||
};
|
};
|
||||||
@ -2705,7 +2716,7 @@ declare type QueryCompilerOptions = {
|
|||||||
declare type QueryEngineBatchGraphQLRequest = {
|
declare type QueryEngineBatchGraphQLRequest = {
|
||||||
batch: QueryEngineRequest[];
|
batch: QueryEngineRequest[];
|
||||||
transaction?: boolean;
|
transaction?: boolean;
|
||||||
isolationLevel?: IsolationLevel;
|
isolationLevel?: IsolationLevel_2;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type QueryEngineBatchRequest = QueryEngineBatchGraphQLRequest | JsonBatchQuery;
|
declare type QueryEngineBatchRequest = QueryEngineBatchGraphQLRequest | JsonBatchQuery;
|
||||||
@ -2851,7 +2862,7 @@ export declare type RenameAndNestPayloadKeys<P> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
declare type RequestBatchOptions<InteractiveTransactionPayload> = {
|
declare type RequestBatchOptions<InteractiveTransactionPayload> = {
|
||||||
transaction?: TransactionOptions_3<InteractiveTransactionPayload>;
|
transaction?: TransactionOptions_2<InteractiveTransactionPayload>;
|
||||||
traceparent?: string;
|
traceparent?: string;
|
||||||
numTry?: number;
|
numTry?: number;
|
||||||
containsWrite: boolean;
|
containsWrite: boolean;
|
||||||
@ -3495,7 +3506,8 @@ declare interface Transaction extends AdapterInfo, SqlQueryable {
|
|||||||
|
|
||||||
declare namespace Transaction_2 {
|
declare namespace Transaction_2 {
|
||||||
export {
|
export {
|
||||||
TransactionOptions_2 as Options,
|
Options,
|
||||||
|
IsolationLevel_2 as IsolationLevel,
|
||||||
InteractiveTransactionInfo,
|
InteractiveTransactionInfo,
|
||||||
TransactionHeaders
|
TransactionHeaders
|
||||||
}
|
}
|
||||||
@ -3509,13 +3521,7 @@ declare type TransactionOptions = {
|
|||||||
usePhantomQuery: boolean;
|
usePhantomQuery: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type TransactionOptions_2 = {
|
declare type TransactionOptions_2<InteractiveTransactionPayload> = {
|
||||||
maxWait?: number;
|
|
||||||
timeout?: number;
|
|
||||||
isolationLevel?: IsolationLevel;
|
|
||||||
};
|
|
||||||
|
|
||||||
declare type TransactionOptions_3<InteractiveTransactionPayload> = {
|
|
||||||
kind: 'itx';
|
kind: 'itx';
|
||||||
options: InteractiveTransactionOptions<InteractiveTransactionPayload>;
|
options: InteractiveTransactionOptions<InteractiveTransactionPayload>;
|
||||||
} | {
|
} | {
|
||||||
|
|||||||
34
src/generated/prisma/runtime/react-native.js
vendored
83
src/generated/prisma/runtime/wasm-compiler-edge.js
Normal file
35
src/generated/prisma/runtime/wasm-engine-edge.js
Normal file
@ -54,7 +54,7 @@ model Team {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Match {
|
model Match {
|
||||||
matchId BigInt @id @default(autoincrement())
|
id String @id @default(uuid())
|
||||||
teamAId String?
|
teamAId String?
|
||||||
teamBId String?
|
teamBId String?
|
||||||
matchDate DateTime
|
matchDate DateTime
|
||||||
@ -78,11 +78,11 @@ model Match {
|
|||||||
|
|
||||||
model MatchPlayer {
|
model MatchPlayer {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
matchId BigInt
|
matchId String
|
||||||
steamId String
|
steamId String
|
||||||
teamId 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])
|
user User @relation(fields: [steamId], references: [steamId])
|
||||||
team Team? @relation(fields: [teamId], references: [id])
|
team Team? @relation(fields: [teamId], references: [id])
|
||||||
stats MatchPlayerStats?
|
stats MatchPlayerStats?
|
||||||
@ -123,14 +123,14 @@ model MatchPlayerStats {
|
|||||||
|
|
||||||
model DemoFile {
|
model DemoFile {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
matchId BigInt @unique
|
matchId String @unique
|
||||||
steamId String
|
steamId String
|
||||||
fileName String @unique
|
fileName String @unique
|
||||||
filePath String
|
filePath String
|
||||||
parsed Boolean @default(false)
|
parsed Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
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])
|
user User @relation(fields: [steamId], references: [steamId])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +163,11 @@ model CS2MatchRequest {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
steamId String
|
steamId String
|
||||||
matchId BigInt
|
matchId String
|
||||||
reservationId BigInt
|
reservationId BigInt
|
||||||
tvPort BigInt
|
tvPort BigInt
|
||||||
processed Boolean @default(false)
|
processed Boolean @default(false)
|
||||||
|
failed Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation("MatchRequests", fields: [userId], references: [steamId])
|
user User @relation("MatchRequests", fields: [userId], references: [steamId])
|
||||||
@ -178,7 +179,7 @@ model PremierRankHistory {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
userId String
|
userId String
|
||||||
steamId String
|
steamId String
|
||||||
matchId BigInt?
|
matchId String? // optionaler String
|
||||||
|
|
||||||
rankOld Int
|
rankOld Int
|
||||||
rankNew Int
|
rankNew Int
|
||||||
@ -187,5 +188,5 @@ model PremierRankHistory {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
user User @relation("UserRankHistory", fields: [userId], references: [steamId])
|
user User @relation("UserRankHistory", fields: [userId], references: [steamId])
|
||||||
match Match? @relation("MatchRankHistory", fields: [matchId], references: [matchId])
|
match Match? @relation("MatchRankHistory", fields: [matchId], references: [id]) // 👈 id statt matchId
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.7.0
|
* Prisma Client JS version: 6.9.0
|
||||||
* Query Engine version: 3cff47a7f5d65c3ea74883f1d736e41d68ce91ed
|
* Query Engine version: 81e4af48011447c3cc503a190e86995b66d2a28e
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.7.0",
|
client: "6.9.0",
|
||||||
engine: "3cff47a7f5d65c3ea74883f1d736e41d68ce91ed"
|
engine: "81e4af48011447c3cc503a190e86995b66d2a28e"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
@ -145,7 +145,7 @@ exports.Prisma.TeamScalarFieldEnum = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.Prisma.MatchScalarFieldEnum = {
|
exports.Prisma.MatchScalarFieldEnum = {
|
||||||
matchId: 'matchId',
|
id: 'id',
|
||||||
teamAId: 'teamAId',
|
teamAId: 'teamAId',
|
||||||
teamBId: 'teamBId',
|
teamBId: 'teamBId',
|
||||||
matchDate: 'matchDate',
|
matchDate: 'matchDate',
|
||||||
@ -232,6 +232,7 @@ exports.Prisma.CS2MatchRequestScalarFieldEnum = {
|
|||||||
reservationId: 'reservationId',
|
reservationId: 'reservationId',
|
||||||
tvPort: 'tvPort',
|
tvPort: 'tvPort',
|
||||||
processed: 'processed',
|
processed: 'processed',
|
||||||
|
failed: 'failed',
|
||||||
createdAt: 'createdAt'
|
createdAt: 'createdAt'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export async function fetchSteamProfile(
|
|||||||
|
|
||||||
if (!res.ok || !contentType.includes('application/json')) {
|
if (!res.ok || !contentType.includes('application/json')) {
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
console.warn(`[SteamAPI] ⚠️ Ungültige Antwort für ${steamId} (${res.status}):`, text.slice(0, 200));
|
console.warn(`[SteamAPI] ⚠️ Ungültige Antwort für ${steamId} (${res.status}):`, text.slice(0, 200));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,27 @@
|
|||||||
export async function getNextShareCodeFromAPI(authCode: string, currentCode: string): Promise<string | null> {
|
// getNextShareCodeFromAPI.ts
|
||||||
try {
|
|
||||||
const res = await fetch('http://localhost:3000/api/cs2/getNextCode', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ authCode, currentCode }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
export async function getNextShareCodeFromAPI(
|
||||||
return data?.nextCode || null;
|
steamId: string,
|
||||||
} catch (err) {
|
authCode: string,
|
||||||
console.error('❌ Fehler beim Abrufen des nächsten ShareCodes:', err);
|
currentCode: string
|
||||||
|
): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:3000/api/cs2/getNextCode', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'x-steamid': steamId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (!data?.valid || !data?.nextCode || data?.nextCode === 'n/a') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return data.nextCode;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`❌ Fehler beim Abrufen des nächsten ShareCodes für ${steamId}:`, err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ interface PlayerStatsExtended {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DemoMatchData {
|
interface DemoMatchData {
|
||||||
matchId: bigint;
|
matchId: string;
|
||||||
matchDate: Date;
|
matchDate: Date;
|
||||||
map: string;
|
map: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
@ -65,16 +65,16 @@ export async function parseAndStoreDemo(
|
|||||||
const relativePath = path.relative(process.cwd(), actualDemoPath);
|
const relativePath = path.relative(process.cwd(), actualDemoPath);
|
||||||
|
|
||||||
const existing = await prisma.match.findUnique({
|
const existing = await prisma.match.findUnique({
|
||||||
where: { matchId: parsed.matchId },
|
where: { id: parsed.matchId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) return null;
|
if (existing) return null;
|
||||||
|
|
||||||
const match = await prisma.match.create({
|
const match = await prisma.match.create({
|
||||||
data: {
|
data: {
|
||||||
|
id: parsed.matchId,
|
||||||
title: `CS2 Match vom ${parsed.matchDate.toLocaleDateString()}`,
|
title: `CS2 Match vom ${parsed.matchDate.toLocaleDateString()}`,
|
||||||
matchDate: parsed.matchDate,
|
matchDate: parsed.matchDate,
|
||||||
matchId: parsed.matchId,
|
|
||||||
map: parsed.map,
|
map: parsed.map,
|
||||||
demoFilePath: relativePath,
|
demoFilePath: relativePath,
|
||||||
matchType: demoPath.endsWith('_premier.dem')
|
matchType: demoPath.endsWith('_premier.dem')
|
||||||
@ -88,7 +88,7 @@ export async function parseAndStoreDemo(
|
|||||||
await prisma.demoFile.create({
|
await prisma.demoFile.create({
|
||||||
data: {
|
data: {
|
||||||
steamId,
|
steamId,
|
||||||
matchId: match.matchId,
|
matchId: match.id,
|
||||||
fileName: path.basename(actualDemoPath),
|
fileName: path.basename(actualDemoPath),
|
||||||
filePath: relativePath,
|
filePath: relativePath,
|
||||||
parsed: true,
|
parsed: true,
|
||||||
@ -103,7 +103,7 @@ export async function parseAndStoreDemo(
|
|||||||
let steamProfile = null;
|
let steamProfile = null;
|
||||||
if (!playerUser?.name || !playerUser?.avatar) {
|
if (!playerUser?.name || !playerUser?.avatar) {
|
||||||
steamProfile = await fetchSteamProfile(player.steamId).catch(() => null);
|
steamProfile = await fetchSteamProfile(player.steamId).catch(() => null);
|
||||||
await delay(500);
|
await delay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPremier = path.basename(actualDemoPath).toLowerCase().endsWith('_premier.dem');
|
const isPremier = path.basename(actualDemoPath).toLowerCase().endsWith('_premier.dem');
|
||||||
@ -148,7 +148,7 @@ export async function parseAndStoreDemo(
|
|||||||
|
|
||||||
const matchPlayer = await prisma.matchPlayer.create({
|
const matchPlayer = await prisma.matchPlayer.create({
|
||||||
data: {
|
data: {
|
||||||
matchId: match.matchId,
|
matchId: match.id,
|
||||||
steamId: player.steamId,
|
steamId: player.steamId,
|
||||||
...(playerUser?.teamId && { teamId: playerUser.teamId }),
|
...(playerUser?.teamId && { teamId: playerUser.teamId }),
|
||||||
},
|
},
|
||||||
@ -211,7 +211,7 @@ export async function parseAndStoreDemo(
|
|||||||
async function parseDemoViaGo(filePath: string, shareCode: string): Promise<DemoMatchData | null> {
|
async function parseDemoViaGo(filePath: string, shareCode: string): Promise<DemoMatchData | null> {
|
||||||
if (!shareCode) throw new Error('❌ Kein ShareCode für MatchId verfügbar');
|
if (!shareCode) throw new Error('❌ Kein ShareCode für MatchId verfügbar');
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const parserPath = path.resolve(__dirname, '../../../cs2-parser/parser_cs2-win.exe');
|
const parserPath = path.resolve(__dirname, '../../../ironie-cs2-parser/parser_cs2-win.exe');
|
||||||
const decoded = decodeMatchShareCode(shareCode);
|
const decoded = decodeMatchShareCode(shareCode);
|
||||||
const matchId = decoded.matchId.toString();
|
const matchId = decoded.matchId.toString();
|
||||||
|
|
||||||
@ -228,7 +228,7 @@ async function parseDemoViaGo(filePath: string, shareCode: string): Promise<Demo
|
|||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(output);
|
const parsed = JSON.parse(output);
|
||||||
resolve({
|
resolve({
|
||||||
matchId: BigInt(parsed.matchId),
|
matchId,
|
||||||
matchDate: new Date(),
|
matchDate: new Date(),
|
||||||
map: parsed.map,
|
map: parsed.map,
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
|
// processAllUsersCron.ts
|
||||||
|
|
||||||
import cron from 'node-cron';
|
import cron from 'node-cron';
|
||||||
import { prisma } from '../app/lib/prisma.js';
|
import { prisma } from '../app/lib/prisma.js';
|
||||||
import { runDownloaderForUser } from './runDownloaderForUser.js';
|
import { runDownloaderForUser } from './runDownloaderForUser.js';
|
||||||
import { sendServerWebSocketMessage } from '../app/lib/websocket-server-client.js';
|
import { sendServerWebSocketMessage } from '../app/lib/websocket-server-client.js';
|
||||||
import { decrypt } from '../app/lib/crypto.js';
|
import { decrypt } from '../app/lib/crypto.js';
|
||||||
import { encodeMatch } from 'csgo-sharecode';
|
import { encodeMatch, decodeMatchShareCode } from 'csgo-sharecode';
|
||||||
import { log } from '../../scripts/cs2-cron-runner.js';
|
import { log } from '../../scripts/cs2-cron-runner.js';
|
||||||
|
import { getNextShareCodeFromAPI } from './getNextShareCodeFromAPI.js';
|
||||||
|
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
|
|
||||||
export function startCS2MatchCron() {
|
export function startCS2MatchCron() {
|
||||||
|
log('🚀 CS2-CronJob Runner gestartet!');
|
||||||
log('🚀 CS2-CronJob Runner gestartet!')
|
|
||||||
|
|
||||||
const job = cron.schedule('* * * * * *', async () => {
|
const job = cron.schedule('* * * * * *', async () => {
|
||||||
await runMatchCheck();
|
await runMatchCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
runMatchCheck(); // direkt beim Start ausführen
|
runMatchCheck(); // direkt beim Start
|
||||||
|
|
||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,106 +33,126 @@ async function runMatchCheck() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
let errorOccurred = false;
|
const decryptedAuthCode = decrypt(user.authCode!);
|
||||||
|
|
||||||
if (!user.authCode) {
|
|
||||||
errorOccurred = true;
|
|
||||||
log(`[${user.steamId}] ⚠️ Kein authCode vorhanden`, "error");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const decryptedAuthCode = decrypt(user.authCode);
|
|
||||||
const allNewMatches = [];
|
const allNewMatches = [];
|
||||||
|
|
||||||
log(`[${user.steamId}] 🔍 Suche nach neuem Match...`);
|
//log(`[${user.steamId}] 🔍 Suche nach neuem Match...`);
|
||||||
|
|
||||||
// 1. ShareCode abrufen
|
let latestKnownCode = user.lastKnownShareCode!;
|
||||||
const res = await fetch('http://localhost:3000/api/cs2/getNextCode', {
|
let nextShareCode = await getNextShareCodeFromAPI(user.steamId, decryptedAuthCode, latestKnownCode);
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'x-steamid': user.steamId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await res.json();
|
if (nextShareCode === null) {
|
||||||
|
// log(`⚠️ Ungültiger lastKnownShareCode bei ${user.steamId}`)
|
||||||
|
|
||||||
if (!data.valid) {
|
const alreadyNotified = await prisma.notification.findFirst({
|
||||||
errorOccurred = true;
|
where: {
|
||||||
log(`🛑 ${data.error}`, "error");
|
userId: user.steamId,
|
||||||
continue;
|
actionType: 'expired-sharecode',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!alreadyNotified) {
|
||||||
|
const notification = await prisma.notification.create({
|
||||||
|
data: {
|
||||||
|
userId: user.steamId,
|
||||||
|
title: 'Share Code abgelaufen',
|
||||||
|
message: 'Dein gespeicherter Share Code ist abgelaufen.',
|
||||||
|
actionType: 'expired-sharecode',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// WebSocket senden
|
||||||
|
await sendServerWebSocketMessage({
|
||||||
|
type: 'expired-sharecode',
|
||||||
|
targetUserIds: [user.steamId],
|
||||||
|
message: notification.message,
|
||||||
|
id: notification.id,
|
||||||
|
actionType: notification.actionType ?? undefined,
|
||||||
|
actionData: notification.actionData ?? undefined,
|
||||||
|
createdAt: notification.createdAt.toISOString(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
continue // springe zum nächsten User
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextCode = data.nextCode;
|
while (nextShareCode) {
|
||||||
if (!nextCode) {
|
const matchInfo = decodeMatchShareCode(nextShareCode);
|
||||||
log(`ℹ️ Keine neuen ShareCodes für ${user.steamId}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`[${user.steamId}] 🆕 Neuer ShareCode gefunden!`);
|
const existingMatch = await prisma.match.findUnique({
|
||||||
|
where: { id: matchInfo.matchId.toString() },
|
||||||
// 2. MatchRequest abrufen
|
|
||||||
const matchRequest = await prisma.cS2MatchRequest.findFirst({
|
|
||||||
where: {
|
|
||||||
steamId: user.steamId,
|
|
||||||
processed: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!matchRequest) {
|
|
||||||
log(`ℹ️ Kein unbearbeiteter MatchRequest für ${user.steamId}`, "warn");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Prüfen, ob Match bereits analysiert wurde
|
|
||||||
const existing = await prisma.match.findUnique({
|
|
||||||
where: {
|
|
||||||
matchId: matchRequest.matchId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existing) {
|
|
||||||
log(`↪️ Match ${matchRequest.matchId} existiert bereits – übersprungen`, "warn");
|
|
||||||
|
|
||||||
await prisma.cS2MatchRequest.update({
|
|
||||||
where: { id: matchRequest.id },
|
|
||||||
data: { processed: true },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
continue;
|
if (existingMatch) {
|
||||||
}
|
log(`↪️ Match ${matchInfo.matchId} existiert bereits – übersprungen`);
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { steamId: user.steamId },
|
||||||
|
data: { lastKnownShareCode: nextShareCode },
|
||||||
|
});
|
||||||
|
latestKnownCode = nextShareCode;
|
||||||
|
nextShareCode = await getNextShareCodeFromAPI(user.steamId, decryptedAuthCode, latestKnownCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Match verarbeiten
|
// Eintragen in cS2MatchRequest (falls noch nicht geschehen)
|
||||||
const shareCode = encodeMatch({
|
await prisma.cS2MatchRequest.upsert({
|
||||||
matchId: matchRequest.matchId,
|
where: {
|
||||||
reservationId: matchRequest.reservationId,
|
steamId_matchId: {
|
||||||
tvPort: Number(matchRequest.tvPort),
|
steamId: user.steamId,
|
||||||
});
|
matchId: matchInfo.matchId.toString(),
|
||||||
|
},
|
||||||
const result = await runDownloaderForUser({
|
},
|
||||||
...user,
|
update: {},
|
||||||
lastKnownShareCode: shareCode,
|
create: {
|
||||||
});
|
userId: user.steamId,
|
||||||
|
steamId: user.steamId,
|
||||||
allNewMatches.push(...result.newMatches);
|
matchId: matchInfo.matchId.toString(),
|
||||||
|
reservationId: matchInfo.reservationId,
|
||||||
if (result.newMatches.length > 0) {
|
tvPort: matchInfo.tvPort,
|
||||||
await prisma.user.update({
|
|
||||||
where: { steamId: user.steamId },
|
|
||||||
data: {
|
|
||||||
lastKnownShareCode: shareCode,
|
|
||||||
lastKnownShareCodeDate: new Date(),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
log(`[${user.steamId}] 🔁 Neuer lastKnownShareCode gesetzt`);
|
const shareCode = encodeMatch(matchInfo);
|
||||||
|
|
||||||
|
const result = await runDownloaderForUser({
|
||||||
|
...user,
|
||||||
|
lastKnownShareCode: shareCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.newMatches.length > 0) {
|
||||||
|
allNewMatches.push(...result.newMatches);
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { steamId: user.steamId },
|
||||||
|
data: {
|
||||||
|
lastKnownShareCode: shareCode,
|
||||||
|
lastKnownShareCodeDate: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.cS2MatchRequest.updateMany({
|
||||||
|
where: { matchId: matchInfo.matchId.toString() },
|
||||||
|
data: { processed: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
latestKnownCode = shareCode;
|
||||||
|
nextShareCode = await getNextShareCodeFromAPI(user.steamId, decryptedAuthCode, latestKnownCode);
|
||||||
|
} else {
|
||||||
|
log(`❌ Parsing fehlgeschlagen für Match ${matchInfo.matchId}`);
|
||||||
|
|
||||||
|
await prisma.cS2MatchRequest.updateMany({
|
||||||
|
where: { matchId: matchInfo.matchId.toString() },
|
||||||
|
data: { failed: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
latestKnownCode = nextShareCode;
|
||||||
|
nextShareCode = await getNextShareCodeFromAPI(user.steamId, decryptedAuthCode, latestKnownCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kleines Delay zwischen Requests
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.cS2MatchRequest.update({
|
|
||||||
where: { id: matchRequest.id },
|
|
||||||
data: { processed: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🧠 Notification & WebSocket
|
|
||||||
if (allNewMatches.length > 0) {
|
if (allNewMatches.length > 0) {
|
||||||
log(`✅ ${allNewMatches.length} neue Matches für ${user.steamId}`);
|
log(`✅ ${allNewMatches.length} neue Matches für ${user.steamId}`);
|
||||||
|
|
||||||
@ -141,15 +161,15 @@ async function runMatchCheck() {
|
|||||||
userId: user.steamId,
|
userId: user.steamId,
|
||||||
title: 'Neue CS2-Matches geladen',
|
title: 'Neue CS2-Matches geladen',
|
||||||
message: `${allNewMatches.length} neue Matches wurden analysiert.`,
|
message: `${allNewMatches.length} neue Matches wurden analysiert.`,
|
||||||
actionType: 'cs2-match',
|
actionType: 'new-cs2-match',
|
||||||
actionData: JSON.stringify({
|
actionData: JSON.stringify({
|
||||||
matchIds: allNewMatches.map(m => m.matchId.toString()),
|
matchIds: allNewMatches.map((m) => m.id), // ← m.id statt m.matchId
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerWebSocketMessage({
|
||||||
type: 'cs2-match',
|
type: 'new-cs2-match',
|
||||||
targetUserIds: [user.steamId],
|
targetUserIds: [user.steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
id: notification.id,
|
id: notification.id,
|
||||||
@ -157,8 +177,8 @@ async function runMatchCheck() {
|
|||||||
actionData: notification.actionData ?? undefined,
|
actionData: notification.actionData ?? undefined,
|
||||||
createdAt: notification.createdAt.toISOString(),
|
createdAt: notification.createdAt.toISOString(),
|
||||||
});
|
});
|
||||||
} else if (!errorOccurred) {
|
} else {
|
||||||
log(`ℹ️ Keine neuen Matches für ${user.steamId}`);
|
//log(`ℹ️ Keine neuen Matches für ${user.steamId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export async function runDownloaderForUser(user: User): Promise<{
|
|||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
newMatches.push(match);
|
newMatches.push(match);
|
||||||
log(`[${steamId}] ✅ Match gespeichert: ${match.matchId}`);
|
log(`[${steamId}] ✅ Match gespeichert: ${match.id}`);
|
||||||
} else {
|
} else {
|
||||||
log(`[${steamId}] ⚠️ Match bereits vorhanden oder Analyse fehlgeschlagen`, 'warn');
|
log(`[${steamId}] ⚠️ Match bereits vorhanden oder Analyse fehlgeschlagen`, 'warn');
|
||||||
}
|
}
|
||||||
|
|||||||