update
This commit is contained in:
parent
de67f784a3
commit
2675c6363c
72
package-lock.json
generated
72
package-lock.json
generated
@ -15,7 +15,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.9.0",
|
"@prisma/client": "^6.10.1",
|
||||||
"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.9.0",
|
"prisma": "^6.10.1",
|
||||||
"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",
|
||||||
@ -1550,9 +1550,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.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.10.1.tgz",
|
||||||
"integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==",
|
"integrity": "sha512-Re4pMlcUsQsUTAYMK7EJ4Bw2kg3WfZAAlr8GjORJaK4VOP6LxRQUQ1TuLnxcF42XqGkWQ36q5CQF1yVadANQ6w==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1572,9 +1572,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/config": {
|
"node_modules/@prisma/config": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.10.1.tgz",
|
||||||
"integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==",
|
"integrity": "sha512-kz4/bnqrOrzWo8KzYguN0cden4CzLJJ+2VSpKtF8utHS3l1JS0Lhv6BLwpOX6X9yNreTbZQZwewb+/BMPDCIYQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1582,53 +1582,53 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/debug": {
|
"node_modules/@prisma/debug": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.10.1.tgz",
|
||||||
"integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==",
|
"integrity": "sha512-k2YT53cWxv9OLjW4zSYTZ6Z7j0gPfCzcr2Mj99qsuvlxr8WAKSZ2NcSR0zLf/mP4oxnYG842IMj3utTgcd7CaA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines": {
|
"node_modules/@prisma/engines": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.10.1.tgz",
|
||||||
"integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==",
|
"integrity": "sha512-Q07P5rS2iPwk2IQr/rUQJ42tHjpPyFcbiH7PXZlV81Ryr9NYIgdxcUrwgVOWVm5T7ap02C0dNd1dpnNcSWig8A==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.9.0",
|
"@prisma/debug": "6.10.1",
|
||||||
"@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
"@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c",
|
||||||
"@prisma/fetch-engine": "6.9.0",
|
"@prisma/fetch-engine": "6.10.1",
|
||||||
"@prisma/get-platform": "6.9.0"
|
"@prisma/get-platform": "6.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/engines-version": {
|
"node_modules/@prisma/engines-version": {
|
||||||
"version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
"version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c.tgz",
|
||||||
"integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==",
|
"integrity": "sha512-ZJFTsEqapiTYVzXya6TUKYDFnSWCNegfUiG5ik9fleQva5Sk3DNyyUi7X1+0ZxWFHwHDr6BZV5Vm+iwP+LlciA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/fetch-engine": {
|
"node_modules/@prisma/fetch-engine": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.10.1.tgz",
|
||||||
"integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==",
|
"integrity": "sha512-clmbG/Jgmrc/n6Y77QcBmAUlq9LrwI9Dbgy4pq5jeEARBpRCWJDJ7PWW1P8p0LfFU0i5fsyO7FqRzRB8mkdS4g==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.9.0",
|
"@prisma/debug": "6.10.1",
|
||||||
"@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e",
|
"@prisma/engines-version": "6.10.1-1.9b628578b3b7cae625e8c927178f15a170e74a9c",
|
||||||
"@prisma/get-platform": "6.9.0"
|
"@prisma/get-platform": "6.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@prisma/get-platform": {
|
"node_modules/@prisma/get-platform": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.10.1.tgz",
|
||||||
"integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==",
|
"integrity": "sha512-4CY5ndKylcsce9Mv+VWp5obbR2/86SHOLVV053pwIkhVtT9C9A83yqiqI/5kJM9T1v1u1qco/bYjDKycmei9HA==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/debug": "6.9.0"
|
"@prisma/debug": "6.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
@ -6237,15 +6237,15 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.10.1.tgz",
|
||||||
"integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==",
|
"integrity": "sha512-khhlC/G49E4+uyA3T3H5PRBut486HD2bDqE2+rvkU0pwk9IAqGFacLFUyIx9Uw+W2eCtf6XGwsp+/strUwMNPw==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@prisma/config": "6.9.0",
|
"@prisma/config": "6.10.1",
|
||||||
"@prisma/engines": "6.9.0"
|
"@prisma/engines": "6.10.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"prisma": "build/index.js"
|
"prisma": "build/index.js"
|
||||||
|
|||||||
@ -18,7 +18,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.9.0",
|
"@prisma/client": "^6.10.1",
|
||||||
"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.9.0",
|
"prisma": "^6.10.1",
|
||||||
"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",
|
||||||
|
|||||||
@ -25,6 +25,9 @@ model User {
|
|||||||
team Team? @relation("UserTeam", fields: [teamId], references: [id])
|
team Team? @relation("UserTeam", fields: [teamId], references: [id])
|
||||||
ledTeam Team? @relation("TeamLeader")
|
ledTeam Team? @relation("TeamLeader")
|
||||||
|
|
||||||
|
matchesAsTeamA Match[] @relation("TeamAPlayers")
|
||||||
|
matchesAsTeamB Match[] @relation("TeamBPlayers")
|
||||||
|
|
||||||
premierRank Int?
|
premierRank Int?
|
||||||
authCode String?
|
authCode String?
|
||||||
lastKnownShareCode String?
|
lastKnownShareCode String?
|
||||||
@ -52,9 +55,10 @@ model Team {
|
|||||||
leader User? @relation("TeamLeader", fields: [leaderId], references: [steamId])
|
leader User? @relation("TeamLeader", fields: [leaderId], references: [steamId])
|
||||||
members User[] @relation("UserTeam")
|
members User[] @relation("UserTeam")
|
||||||
invites TeamInvite[]
|
invites TeamInvite[]
|
||||||
matchesAsTeamA Match[] @relation("Match_TeamA")
|
|
||||||
matchesAsTeamB Match[] @relation("Match_TeamB")
|
|
||||||
matchPlayers MatchPlayer[]
|
matchPlayers MatchPlayer[]
|
||||||
|
|
||||||
|
matchesAsTeamA Match[] @relation("MatchTeamA")
|
||||||
|
matchesAsTeamB Match[] @relation("MatchTeamB")
|
||||||
}
|
}
|
||||||
|
|
||||||
model TeamInvite {
|
model TeamInvite {
|
||||||
@ -98,9 +102,13 @@ model Match {
|
|||||||
scoreB Int?
|
scoreB Int?
|
||||||
|
|
||||||
teamAId String?
|
teamAId String?
|
||||||
|
teamA Team? @relation("MatchTeamA", fields: [teamAId], references: [id])
|
||||||
|
|
||||||
teamBId String?
|
teamBId String?
|
||||||
teamA Team? @relation("Match_TeamA", fields: [teamAId], references: [id])
|
teamB Team? @relation("MatchTeamB", fields: [teamBId], references: [id])
|
||||||
teamB Team? @relation("Match_TeamB", fields: [teamBId], references: [id])
|
|
||||||
|
teamAUsers User[] @relation("TeamAPlayers")
|
||||||
|
teamBUsers User[] @relation("TeamBPlayers")
|
||||||
|
|
||||||
filePath String?
|
filePath String?
|
||||||
demoFile DemoFile?
|
demoFile DemoFile?
|
||||||
|
|||||||
@ -14,8 +14,6 @@ export async function GET(_: Request, context: { params: { id: string } }) {
|
|||||||
const match = await prisma.match.findUnique({
|
const match = await prisma.match.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
teamA: true,
|
|
||||||
teamB: true,
|
|
||||||
players: {
|
players: {
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
@ -23,6 +21,16 @@ export async function GET(_: Request, context: { params: { id: string } }) {
|
|||||||
team: true,
|
team: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
teamAUsers: {
|
||||||
|
include: {
|
||||||
|
team: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
teamBUsers: {
|
||||||
|
include: {
|
||||||
|
team: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -30,49 +38,38 @@ export async function GET(_: Request, context: { params: { id: string } }) {
|
|||||||
return NextResponse.json({ error: 'Match nicht gefunden' }, { status: 404 })
|
return NextResponse.json({ error: 'Match nicht gefunden' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const teamAIds = new Set(match.teamAUsers.map(u => u.steamId));
|
||||||
|
const teamBIds = new Set(match.teamBUsers.map(u => u.steamId));
|
||||||
|
|
||||||
const playersA = match.players
|
const playersA = match.players
|
||||||
.filter(p => p.teamId === match.teamAId)
|
.filter(p => teamAIds.has(p.steamId))
|
||||||
.map(p => ({
|
.map(p => ({
|
||||||
user: p.user,
|
user: p.user,
|
||||||
stats: p.stats,
|
stats: p.stats,
|
||||||
team: p.team?.name ?? 'CT',
|
team: p.team?.name ?? 'Team A',
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const playersB = match.players
|
const playersB = match.players
|
||||||
.filter(p => p.teamId === match.teamBId)
|
.filter(p => teamBIds.has(p.steamId))
|
||||||
.map(p => ({
|
.map(p => ({
|
||||||
user: p.user,
|
user: p.user,
|
||||||
stats: p.stats,
|
stats: p.stats,
|
||||||
team: p.team?.name ?? 'T',
|
team: p.team?.name ?? 'Team B',
|
||||||
}))
|
}));
|
||||||
|
|
||||||
const teamA = match.teamA
|
const teamA = {
|
||||||
? {
|
name: match.teamAUsers[0]?.team?.name ?? 'Team A',
|
||||||
id: match.teamA.id,
|
|
||||||
name: match.teamA.name,
|
|
||||||
logo: match.teamA.logo,
|
|
||||||
players: playersA,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: null,
|
|
||||||
name: 'CT',
|
|
||||||
logo: null,
|
logo: null,
|
||||||
|
score: match.scoreA,
|
||||||
players: playersA,
|
players: playersA,
|
||||||
}
|
};
|
||||||
|
|
||||||
const teamB = match.teamB
|
const teamB = {
|
||||||
? {
|
name: match.teamBUsers[0]?.team?.name ?? 'Team B',
|
||||||
id: match.teamB.id,
|
|
||||||
name: match.teamB.name,
|
|
||||||
logo: match.teamB.logo,
|
|
||||||
players: playersB,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
id: null,
|
|
||||||
name: 'T',
|
|
||||||
logo: null,
|
logo: null,
|
||||||
|
score: match.scoreB,
|
||||||
players: playersB,
|
players: playersB,
|
||||||
}
|
};
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
id: match.id,
|
id: match.id,
|
||||||
@ -81,11 +78,9 @@ export async function GET(_: Request, context: { params: { id: string } }) {
|
|||||||
demoDate: match.demoDate,
|
demoDate: match.demoDate,
|
||||||
matchType: match.matchType,
|
matchType: match.matchType,
|
||||||
map: match.map,
|
map: match.map,
|
||||||
scoreA: match.scoreA,
|
|
||||||
scoreB: match.scoreB,
|
|
||||||
teamA,
|
teamA,
|
||||||
teamB,
|
teamB,
|
||||||
})
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`GET /matches/${id} failed:`, err)
|
console.error(`GET /matches/${id} failed:`, err)
|
||||||
return NextResponse.json({ error: 'Failed to load match' }, { status: 500 })
|
return NextResponse.json({ error: 'Failed to load match' }, { status: 500 })
|
||||||
@ -105,20 +100,21 @@ export async function PUT(req: NextRequest, context: { params: { id: string } })
|
|||||||
const body = await req.json()
|
const body = await req.json()
|
||||||
const { title, description, matchDate, players } = body
|
const { title, description, matchDate, players } = body
|
||||||
|
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { steamId: userId },
|
||||||
|
include: { ledTeam: true },
|
||||||
|
});
|
||||||
|
|
||||||
const match = await prisma.match.findUnique({
|
const match = await prisma.match.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
});
|
||||||
teamA: { include: { leader: true } },
|
|
||||||
teamB: { include: { leader: true } },
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return NextResponse.json({ error: 'Match not found' }, { status: 404 })
|
return NextResponse.json({ error: 'Match not found' }, { status: 404 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTeamLeaderA = match.teamA?.leaderId === userId
|
const isTeamLeaderA = match.teamAId && user?.ledTeam?.id === match.teamAId;
|
||||||
const isTeamLeaderB = match.teamB?.leaderId === userId
|
const isTeamLeaderB = match.teamBId && user?.ledTeam?.id === match.teamBId;
|
||||||
|
|
||||||
if (!isAdmin && !isTeamLeaderA && !isTeamLeaderB) {
|
if (!isAdmin && !isTeamLeaderA && !isTeamLeaderB) {
|
||||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||||
@ -161,8 +157,6 @@ export async function PUT(req: NextRequest, context: { params: { id: string } })
|
|||||||
const updated = await prisma.match.findUnique({
|
const updated = await prisma.match.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
teamA: true,
|
|
||||||
teamB: true,
|
|
||||||
players: {
|
players: {
|
||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
// /app/api/matches/route.ts
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { prisma } from '@/app/lib/prisma'
|
import { prisma } from '@/app/lib/prisma'
|
||||||
|
|
||||||
@ -12,12 +13,43 @@ export async function GET() {
|
|||||||
include: {
|
include: {
|
||||||
user: true,
|
user: true,
|
||||||
stats: true,
|
stats: true,
|
||||||
|
team: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return NextResponse.json(matches)
|
const formatted = matches.map(match => ({
|
||||||
|
id: match.id,
|
||||||
|
map: match.map,
|
||||||
|
demoDate: match.demoDate,
|
||||||
|
matchType: match.matchType,
|
||||||
|
scoreA: match.scoreA,
|
||||||
|
scoreB: match.scoreB,
|
||||||
|
winnerTeam: match.winnerTeam ?? null,
|
||||||
|
teamA: {
|
||||||
|
id: match.teamA?.id ?? null,
|
||||||
|
name: match.teamA?.name ?? 'CT',
|
||||||
|
logo: match.teamA?.logo ?? null,
|
||||||
|
score: match.scoreA,
|
||||||
|
},
|
||||||
|
teamB: {
|
||||||
|
id: match.teamB?.id ?? null,
|
||||||
|
name: match.teamB?.name ?? 'T',
|
||||||
|
logo: match.teamB?.logo ?? null,
|
||||||
|
score: match.scoreB,
|
||||||
|
},
|
||||||
|
players: match.players.map(p => ({
|
||||||
|
steamId: p.steamId,
|
||||||
|
name: p.user?.name,
|
||||||
|
avatar: p.user?.avatar,
|
||||||
|
stats: p.stats,
|
||||||
|
teamId: p.teamId,
|
||||||
|
teamName: p.team?.name ?? null,
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return NextResponse.json(formatted)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('GET /matches failed:', err)
|
console.error('GET /matches failed:', err)
|
||||||
return NextResponse.json({ error: 'Failed to load matches' }, { status: 500 })
|
return NextResponse.json({ error: 'Failed to load matches' }, { status: 500 })
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export async function GET(req: NextRequest) {
|
|||||||
const matchPlayers = await prisma.matchPlayer.findMany({
|
const matchPlayers = await prisma.matchPlayer.findMany({
|
||||||
where: { steamId: steamId },
|
where: { steamId: steamId },
|
||||||
select: {
|
select: {
|
||||||
|
teamId: true,
|
||||||
team: true,
|
team: true,
|
||||||
match: {
|
match: {
|
||||||
select: {
|
select: {
|
||||||
@ -26,6 +27,8 @@ export async function GET(req: NextRequest) {
|
|||||||
matchType: true,
|
matchType: true,
|
||||||
teamAId: true,
|
teamAId: true,
|
||||||
teamBId: true,
|
teamBId: true,
|
||||||
|
winnerTeam: true,
|
||||||
|
demoData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
stats: true,
|
stats: true,
|
||||||
@ -45,13 +48,28 @@ export async function GET(req: NextRequest) {
|
|||||||
const kd = deaths > 0 ? (kills / deaths).toFixed(2) : '∞';
|
const kd = deaths > 0 ? (kills / deaths).toFixed(2) : '∞';
|
||||||
const rankOld = stats?.rankOld ?? null;
|
const rankOld = stats?.rankOld ?? null;
|
||||||
const rankNew = stats?.rankNew ?? null;
|
const rankNew = stats?.rankNew ?? null;
|
||||||
const rankChange = typeof rankNew === 'number' && typeof rankOld === 'number' ? rankNew - rankOld : null;
|
const rankChange =
|
||||||
|
typeof rankNew === 'number' && typeof rankOld === 'number'
|
||||||
|
? rankNew - rankOld
|
||||||
|
: null;
|
||||||
const matchType = match.matchType ?? 'community';
|
const matchType = match.matchType ?? 'community';
|
||||||
|
const demoData = match.demoData as any;
|
||||||
|
|
||||||
// Spieler war Team A, wenn seine teamId == match.teamAId
|
// Spielerteam: CT oder T
|
||||||
const isTeamA = mp.team === 'CT';
|
let playerTeam: string | null = null;
|
||||||
const scoreLeft = isTeamA ? match.scoreA : match.scoreB;
|
let isInTeamA = false;
|
||||||
const scoreRight = isTeamA ? match.scoreB : match.scoreA;
|
let isInTeamB = false;
|
||||||
|
|
||||||
|
if (demoData?.teamA?.players && demoData?.teamB?.players) {
|
||||||
|
isInTeamA = demoData.teamA.players.some((p: any) => p?.steamId === steamId);
|
||||||
|
isInTeamB = demoData.teamB.players.some((p: any) => p?.steamId === steamId);
|
||||||
|
|
||||||
|
if (isInTeamA) playerTeam = 'CT';
|
||||||
|
if (isInTeamB) playerTeam = 'T';
|
||||||
|
}
|
||||||
|
|
||||||
|
const scoreLeft = isInTeamA ? match.scoreA : match.scoreB;
|
||||||
|
const scoreRight = isInTeamB ? match.scoreB : match.scoreA;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: match.id,
|
id: match.id,
|
||||||
@ -65,6 +83,8 @@ export async function GET(req: NextRequest) {
|
|||||||
kills,
|
kills,
|
||||||
deaths,
|
deaths,
|
||||||
kd,
|
kd,
|
||||||
|
winnerTeam: match.winnerTeam ?? null,
|
||||||
|
team: mp.team?.name ?? null,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -45,15 +45,14 @@ export default function CompRankBadge({ rank }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={altText}>
|
<Tooltip content={altText}>
|
||||||
<div style={{ position: 'relative', width: 70, height: 40 }}>
|
|
||||||
<Image
|
<Image
|
||||||
src={`/assets/img/skillgroups/${imageName}`}
|
src={`/assets/img/skillgroups/${imageName}`}
|
||||||
alt={altText}
|
alt={altText}
|
||||||
fill
|
width={60}
|
||||||
style={{ objectFit: 'contain' }}
|
height={60}
|
||||||
sizes="(max-width: 768px) 100px, 70px"
|
sizes="(max-width: 768px) 100px, 70px"
|
||||||
|
style={{ objectFit: 'contain' }} // ← korrekt!
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import InvitePlayersModal from './InvitePlayersModal'
|
|||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
import { Player, Team } from '../types/team'
|
import { Player, Team } from '../types/team'
|
||||||
import { useSession } from 'next-auth/react'
|
import { useSession } from 'next-auth/react'
|
||||||
import { useWS } from '@/app/lib/useSSEStore'
|
import { useSSE } from '@/app/lib/useSSEStore'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { AnimatePresence, motion } from 'framer-motion'
|
||||||
import { useTeamManager } from '../hooks/useTeamManager'
|
import { useTeamManager } from '../hooks/useTeamManager'
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
@ -51,7 +51,7 @@ export default function TeamMemberView({
|
|||||||
setInactivePlayers,
|
setInactivePlayers,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const { socket } = useWS()
|
const { source, connect } = useSSE()
|
||||||
const [kickCandidate, setKickCandidate] = useState<Player | null>(null)
|
const [kickCandidate, setKickCandidate] = useState<Player | null>(null)
|
||||||
const [promoteCandidate, setPromoteCandidate] = useState<Player | null>(null)
|
const [promoteCandidate, setPromoteCandidate] = useState<Player | null>(null)
|
||||||
|
|
||||||
@ -67,7 +67,14 @@ export default function TeamMemberView({
|
|||||||
const [logoFile, setLogoFile] = useState<File | null>(null)
|
const [logoFile, setLogoFile] = useState<File | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket || !team?.id) return
|
if (session?.user?.steamId) {
|
||||||
|
connect(session.user.steamId)
|
||||||
|
}
|
||||||
|
}, [session?.user?.steamId])
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!source || !team?.id) return
|
||||||
|
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
@ -102,9 +109,9 @@ export default function TeamMemberView({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.addEventListener('message', handleMessage)
|
source.addEventListener('message', handleMessage)
|
||||||
return () => socket.removeEventListener('message', handleMessage)
|
return () => source.removeEventListener('message', handleMessage)
|
||||||
}, [socket, team?.id])
|
}, [source, team?.id])
|
||||||
|
|
||||||
|
|
||||||
const handleDragStart = (event: any) => {
|
const handleDragStart = (event: any) => {
|
||||||
|
|||||||
@ -13,6 +13,8 @@ interface Match {
|
|||||||
map: string;
|
map: string;
|
||||||
date: string;
|
date: string;
|
||||||
score: string;
|
score: string;
|
||||||
|
winnerTeam?: string;
|
||||||
|
team?: 'CT' | 'T';
|
||||||
matchType: string;
|
matchType: string;
|
||||||
rating: string;
|
rating: string;
|
||||||
kills: number;
|
kills: number;
|
||||||
@ -50,7 +52,17 @@ export default function UserMatchesTable() {
|
|||||||
<Table.Body>
|
<Table.Body>
|
||||||
{matches.map((m) => {
|
{matches.map((m) => {
|
||||||
const mapInfo = mapNameMap[m.map] ?? mapNameMap['lobby_mapveto'];
|
const mapInfo = mapNameMap[m.map] ?? mapNameMap['lobby_mapveto'];
|
||||||
const [left, right] = m.score.split(':').map(s => parseInt(s.trim(), 10));
|
const [scoreCT, scoreT] = m.score.split(':').map(s => parseInt(s.trim(), 10));
|
||||||
|
|
||||||
|
let left = scoreCT;
|
||||||
|
let right = scoreT;
|
||||||
|
|
||||||
|
// Score-Reihenfolge anhand des eigenen Teams und Sieger drehen
|
||||||
|
if (m.team === 'T') {
|
||||||
|
left = scoreT;
|
||||||
|
right = scoreCT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Score-Farbe bestimmen
|
// Score-Farbe bestimmen
|
||||||
let scoreClass = '';
|
let scoreClass = '';
|
||||||
|
|||||||
@ -65,12 +65,21 @@ export const authOptions = (req: NextRequest): NextAuthOptions => ({
|
|||||||
return session
|
return session
|
||||||
},
|
},
|
||||||
|
|
||||||
async redirect({ url, baseUrl }) {
|
redirect({ url, baseUrl }) {
|
||||||
if (url.includes('/api/auth/signout')) {
|
const isSignIn = url.startsWith(`${baseUrl}/api/auth/signin`);
|
||||||
return `${baseUrl}/` // Zurück zur Startseite
|
const isSignOut = url.startsWith(`${baseUrl}/api/auth/signout`);
|
||||||
|
|
||||||
|
if (isSignOut) {
|
||||||
|
return `${baseUrl}/`; // Nach Logout auf Startseite
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard-Redirect nach Login
|
||||||
|
if (isSignIn || url === baseUrl) {
|
||||||
|
return `${baseUrl}/dashboard`; // z. B. Dashboard als Startpunkt
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.startsWith(baseUrl) ? url : baseUrl;
|
||||||
}
|
}
|
||||||
return `${baseUrl}/dashboard`
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -12,12 +12,11 @@ export const useSSE = create<SSEState>((set, get) => {
|
|||||||
|
|
||||||
const connect = (steamId: string): EventSource | undefined => {
|
const connect = (steamId: string): EventSource | undefined => {
|
||||||
const current = get().source
|
const current = get().source
|
||||||
if (current) return current // bereits verbunden
|
if (current) return current
|
||||||
|
|
||||||
const source = new EventSource(`http://localhost:3001/events?steamId=${steamId}`)
|
const source = new EventSource(`http://localhost:3001/events?steamId=${steamId}`)
|
||||||
|
|
||||||
source.onopen = () => {
|
source.onopen = () => {
|
||||||
console.log('[SSE] Verbunden')
|
|
||||||
set({ source, isConnected: true })
|
set({ source, isConnected: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.9.0
|
* Prisma Client JS version: 6.10.1
|
||||||
* Query Engine version: 81e4af48011447c3cc503a190e86995b66d2a28e
|
* Query Engine version: 9b628578b3b7cae625e8c927178f15a170e74a9c
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.9.0",
|
client: "6.10.1",
|
||||||
engine: "81e4af48011447c3cc503a190e86995b66d2a28e"
|
engine: "9b628578b3b7cae625e8c927178f15a170e74a9c"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
|||||||
1378
src/generated/prisma/index.d.ts
vendored
1378
src/generated/prisma/index.d.ts
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "prisma-client-7dc6d5614c138eb3659cdd5dbb69f5d3b2be84ae9983dbfa39f9833b9ad6da8c",
|
"name": "prisma-client-81fe4a88a75a445ee239171472edc8b1edb558143434347db08d32212685268e",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"browser": "index-browser.js",
|
"browser": "index-browser.js",
|
||||||
@ -141,6 +141,6 @@
|
|||||||
},
|
},
|
||||||
"./*": "./*"
|
"./*": "./*"
|
||||||
},
|
},
|
||||||
"version": "6.9.0",
|
"version": "6.10.1",
|
||||||
"sideEffects": false
|
"sideEffects": false
|
||||||
}
|
}
|
||||||
Binary file not shown.
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp12760
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp12760
Normal file
Binary file not shown.
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp23572
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp23572
Normal file
Binary file not shown.
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp24776
Normal file
BIN
src/generated/prisma/query_engine-windows.dll.node.tmp24776
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
36
src/generated/prisma/runtime/library.d.ts
vendored
36
src/generated/prisma/runtime/library.d.ts
vendored
@ -209,7 +209,7 @@ declare const ColumnTypeEnum: {
|
|||||||
|
|
||||||
declare type CompactedBatchResponse = {
|
declare type CompactedBatchResponse = {
|
||||||
type: 'compacted';
|
type: 'compacted';
|
||||||
plan: object;
|
plan: {};
|
||||||
arguments: Record<string, {}>[];
|
arguments: Record<string, {}>[];
|
||||||
nestedSelection: string[];
|
nestedSelection: string[];
|
||||||
keys: string[];
|
keys: string[];
|
||||||
@ -255,6 +255,7 @@ declare type ComputedFieldsMap = {
|
|||||||
declare type ConnectionInfo = {
|
declare type ConnectionInfo = {
|
||||||
schemaName?: string;
|
schemaName?: string;
|
||||||
maxBindValues?: number;
|
maxBindValues?: number;
|
||||||
|
supportsRelationJoins: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type ConnectorType = 'mysql' | 'mongodb' | 'sqlite' | 'postgresql' | 'postgres' | 'prisma+postgres' | 'sqlserver' | 'cockroachdb';
|
declare type ConnectorType = 'mysql' | 'mongodb' | 'sqlite' | 'postgresql' | 'postgres' | 'prisma+postgres' | 'sqlserver' | 'cockroachdb';
|
||||||
@ -1153,10 +1154,22 @@ declare type Error_2 = {
|
|||||||
column?: string;
|
column?: string;
|
||||||
} | {
|
} | {
|
||||||
kind: 'UniqueConstraintViolation';
|
kind: 'UniqueConstraintViolation';
|
||||||
|
constraint?: {
|
||||||
fields: string[];
|
fields: string[];
|
||||||
|
} | {
|
||||||
|
index: string;
|
||||||
|
} | {
|
||||||
|
foreignKey: {};
|
||||||
|
};
|
||||||
} | {
|
} | {
|
||||||
kind: 'NullConstraintViolation';
|
kind: 'NullConstraintViolation';
|
||||||
|
constraint?: {
|
||||||
fields: string[];
|
fields: string[];
|
||||||
|
} | {
|
||||||
|
index: string;
|
||||||
|
} | {
|
||||||
|
foreignKey: {};
|
||||||
|
};
|
||||||
} | {
|
} | {
|
||||||
kind: 'ForeignKeyConstraintViolation';
|
kind: 'ForeignKeyConstraintViolation';
|
||||||
constraint?: {
|
constraint?: {
|
||||||
@ -1189,8 +1202,19 @@ declare type Error_2 = {
|
|||||||
} | {
|
} | {
|
||||||
kind: 'TooManyConnections';
|
kind: 'TooManyConnections';
|
||||||
cause: string;
|
cause: string;
|
||||||
|
} | {
|
||||||
|
kind: 'ValueOutOfRange';
|
||||||
|
cause: string;
|
||||||
|
} | {
|
||||||
|
kind: 'MissingFullTextSearchIndex';
|
||||||
} | {
|
} | {
|
||||||
kind: 'SocketTimeout';
|
kind: 'SocketTimeout';
|
||||||
|
} | {
|
||||||
|
kind: 'InconsistentColumnData';
|
||||||
|
cause: string;
|
||||||
|
} | {
|
||||||
|
kind: 'TransactionAlreadyClosed';
|
||||||
|
cause: string;
|
||||||
} | {
|
} | {
|
||||||
kind: 'postgres';
|
kind: 'postgres';
|
||||||
code: string;
|
code: string;
|
||||||
@ -1211,6 +1235,10 @@ declare type Error_2 = {
|
|||||||
*/
|
*/
|
||||||
extendedCode: number;
|
extendedCode: number;
|
||||||
message: string;
|
message: string;
|
||||||
|
} | {
|
||||||
|
kind: 'mssql';
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare type ErrorCapturingFunction<T> = T extends (...args: infer A) => Promise<infer R> ? (...args: A) => Promise<Result_4<ErrorCapturingInterface<R>>> : T extends (...args: infer A) => infer R ? (...args: A) => Result_4<ErrorCapturingInterface<R>> : T;
|
declare type ErrorCapturingFunction<T> = T extends (...args: infer A) => Promise<infer R> ? (...args: A) => Promise<Result_4<ErrorCapturingInterface<R>>> : T extends (...args: infer A) => infer R ? (...args: A) => Result_4<ErrorCapturingInterface<R>> : T;
|
||||||
@ -2383,7 +2411,7 @@ export declare const objectEnumValues: {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
declare const officialPrismaAdapters: readonly ["@prisma/adapter-planetscale", "@prisma/adapter-neon", "@prisma/adapter-libsql", "@prisma/adapter-d1", "@prisma/adapter-pg", "@prisma/adapter-pg-worker"];
|
declare const officialPrismaAdapters: readonly ["@prisma/adapter-planetscale", "@prisma/adapter-neon", "@prisma/adapter-libsql", "@prisma/adapter-d1", "@prisma/adapter-pg", "@prisma/adapter-mssql"];
|
||||||
|
|
||||||
export declare type Omission = Record<string, boolean | Skip>;
|
export declare type Omission = Record<string, boolean | Skip>;
|
||||||
|
|
||||||
@ -2661,7 +2689,7 @@ declare type PrismaPromiseTransaction<PayloadType = unknown> = PrismaPromiseBatc
|
|||||||
|
|
||||||
export declare const PrivateResultType: unique symbol;
|
export declare const PrivateResultType: unique symbol;
|
||||||
|
|
||||||
declare type Provider = 'mysql' | 'postgres' | 'sqlite';
|
declare type Provider = 'mysql' | 'postgres' | 'sqlite' | 'sqlserver';
|
||||||
|
|
||||||
declare namespace Public {
|
declare namespace Public {
|
||||||
export {
|
export {
|
||||||
@ -2699,7 +2727,7 @@ declare interface Queryable<Query, Result> extends AdapterInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare type QueryCompiler = {
|
declare type QueryCompiler = {
|
||||||
compile(request: string): string;
|
compile(request: string): {};
|
||||||
compileBatch(batchRequest: string): BatchResponse;
|
compileBatch(batchRequest: string): BatchResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
4
src/generated/prisma/runtime/react-native.js
vendored
4
src/generated/prisma/runtime/react-native.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -25,6 +25,9 @@ model User {
|
|||||||
team Team? @relation("UserTeam", fields: [teamId], references: [id])
|
team Team? @relation("UserTeam", fields: [teamId], references: [id])
|
||||||
ledTeam Team? @relation("TeamLeader")
|
ledTeam Team? @relation("TeamLeader")
|
||||||
|
|
||||||
|
matchesAsTeamA Match[] @relation("TeamAPlayers")
|
||||||
|
matchesAsTeamB Match[] @relation("TeamBPlayers")
|
||||||
|
|
||||||
premierRank Int?
|
premierRank Int?
|
||||||
authCode String?
|
authCode String?
|
||||||
lastKnownShareCode String?
|
lastKnownShareCode String?
|
||||||
@ -52,9 +55,10 @@ model Team {
|
|||||||
leader User? @relation("TeamLeader", fields: [leaderId], references: [steamId])
|
leader User? @relation("TeamLeader", fields: [leaderId], references: [steamId])
|
||||||
members User[] @relation("UserTeam")
|
members User[] @relation("UserTeam")
|
||||||
invites TeamInvite[]
|
invites TeamInvite[]
|
||||||
matchesAsTeamA Match[] @relation("Match_TeamA")
|
|
||||||
matchesAsTeamB Match[] @relation("Match_TeamB")
|
|
||||||
matchPlayers MatchPlayer[]
|
matchPlayers MatchPlayer[]
|
||||||
|
|
||||||
|
matchesAsTeamA Match[] @relation("MatchTeamA")
|
||||||
|
matchesAsTeamB Match[] @relation("MatchTeamB")
|
||||||
}
|
}
|
||||||
|
|
||||||
model TeamInvite {
|
model TeamInvite {
|
||||||
@ -98,9 +102,13 @@ model Match {
|
|||||||
scoreB Int?
|
scoreB Int?
|
||||||
|
|
||||||
teamAId String?
|
teamAId String?
|
||||||
|
teamA Team? @relation("MatchTeamA", fields: [teamAId], references: [id])
|
||||||
|
|
||||||
teamBId String?
|
teamBId String?
|
||||||
teamA Team? @relation("Match_TeamA", fields: [teamAId], references: [id])
|
teamB Team? @relation("MatchTeamB", fields: [teamBId], references: [id])
|
||||||
teamB Team? @relation("Match_TeamB", fields: [teamBId], references: [id])
|
|
||||||
|
teamAUsers User[] @relation("TeamAPlayers")
|
||||||
|
teamBUsers User[] @relation("TeamBPlayers")
|
||||||
|
|
||||||
filePath String?
|
filePath String?
|
||||||
demoFile DemoFile?
|
demoFile DemoFile?
|
||||||
|
|||||||
@ -20,12 +20,12 @@ exports.Prisma = Prisma
|
|||||||
exports.$Enums = {}
|
exports.$Enums = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prisma Client JS version: 6.9.0
|
* Prisma Client JS version: 6.10.1
|
||||||
* Query Engine version: 81e4af48011447c3cc503a190e86995b66d2a28e
|
* Query Engine version: 9b628578b3b7cae625e8c927178f15a170e74a9c
|
||||||
*/
|
*/
|
||||||
Prisma.prismaVersion = {
|
Prisma.prismaVersion = {
|
||||||
client: "6.9.0",
|
client: "6.10.1",
|
||||||
engine: "81e4af48011447c3cc503a190e86995b66d2a28e"
|
engine: "9b628578b3b7cae625e8c927178f15a170e74a9c"
|
||||||
}
|
}
|
||||||
|
|
||||||
Prisma.PrismaClientKnownRequestError = () => {
|
Prisma.PrismaClientKnownRequestError = () => {
|
||||||
|
|||||||
@ -38,21 +38,24 @@ interface DemoMatchData {
|
|||||||
map: string;
|
map: string;
|
||||||
filePath: string;
|
filePath: string;
|
||||||
meta: {
|
meta: {
|
||||||
tickRate: number;
|
demoDate?: Date;
|
||||||
duration: number;
|
teamA?: {
|
||||||
map: string;
|
name: string;
|
||||||
|
score: number;
|
||||||
players: PlayerStatsExtended[];
|
players: PlayerStatsExtended[];
|
||||||
scoreCT?: number;
|
};
|
||||||
scoreT?: number;
|
teamB?: {
|
||||||
|
name: string;
|
||||||
|
score: number;
|
||||||
|
players: PlayerStatsExtended[];
|
||||||
|
};
|
||||||
winnerTeam?: string;
|
winnerTeam?: string;
|
||||||
roundCount?: number;
|
roundCount?: number;
|
||||||
roundHistory?: { round: number; winner: string; winReason: string }[];
|
roundHistory?: { round: number; winner: string; winReason: string }[];
|
||||||
demoDate?: Date;
|
|
||||||
teamCT: PlayerStatsExtended[];
|
|
||||||
teamT: PlayerStatsExtended[];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function parseAndStoreDemo(
|
export async function parseAndStoreDemo(
|
||||||
demoPath: string,
|
demoPath: string,
|
||||||
steamId: string,
|
steamId: string,
|
||||||
@ -62,6 +65,7 @@ export async function parseAndStoreDemo(
|
|||||||
if (!parsed) return null;
|
if (!parsed) return null;
|
||||||
|
|
||||||
let actualDemoPath = demoPath;
|
let actualDemoPath = demoPath;
|
||||||
|
|
||||||
if (parsed.map && parsed.map !== 'unknownmap' && demoPath.includes('unknownmap')) {
|
if (parsed.map && parsed.map !== 'unknownmap' && demoPath.includes('unknownmap')) {
|
||||||
const oldName = path.basename(demoPath);
|
const oldName = path.basename(demoPath);
|
||||||
const newName = oldName.replace('unknownmap', parsed.map);
|
const newName = oldName.replace('unknownmap', parsed.map);
|
||||||
@ -92,9 +96,57 @@ export async function parseAndStoreDemo(
|
|||||||
const existing = await prisma.match.findUnique({
|
const existing = await prisma.match.findUnique({
|
||||||
where: { id: parsed.matchId },
|
where: { id: parsed.matchId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) return null;
|
if (existing) return null;
|
||||||
|
|
||||||
|
const teamAIds: string[] = [];
|
||||||
|
const teamBIds: string[] = [];
|
||||||
|
|
||||||
|
const allPlayers = [
|
||||||
|
...(parsed.meta.teamA?.players || []),
|
||||||
|
...(parsed.meta.teamB?.players || []),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const player of allPlayers) {
|
||||||
|
let playerUser = await prisma.user.findUnique({
|
||||||
|
where: { steamId: player.steamId },
|
||||||
|
});
|
||||||
|
|
||||||
|
let steamProfile = null;
|
||||||
|
if (!playerUser?.name || !playerUser?.avatar) {
|
||||||
|
steamProfile = await fetchSteamProfile(player.steamId).catch(() => null);
|
||||||
|
await delay(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPremier = path.basename(actualDemoPath).toLowerCase().endsWith('_premier.dem');
|
||||||
|
const updatedFields: Partial<{ name: string; avatar: string; premierRank: number }> = {};
|
||||||
|
|
||||||
|
if (!playerUser) {
|
||||||
|
await prisma.user.create({
|
||||||
|
data: {
|
||||||
|
steamId: player.steamId,
|
||||||
|
name: steamProfile?.name ?? player.name,
|
||||||
|
avatar: steamProfile?.avatar ?? undefined,
|
||||||
|
premierRank: isPremier ? player.rankNew ?? undefined : undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (steamProfile?.name && playerUser.name !== steamProfile.name) updatedFields.name = steamProfile.name;
|
||||||
|
if (steamProfile?.avatar && playerUser.avatar !== steamProfile.avatar) updatedFields.avatar = steamProfile.avatar;
|
||||||
|
if (Object.keys(updatedFields).length > 0) {
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { steamId: player.steamId },
|
||||||
|
data: updatedFields,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.meta.teamA?.players.some(p => p.steamId === player.steamId)) {
|
||||||
|
teamAIds.push(player.steamId);
|
||||||
|
} else if (parsed.meta.teamB?.players.some(p => p.steamId === player.steamId)) {
|
||||||
|
teamBIds.push(player.steamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const match = await prisma.match.create({
|
const match = await prisma.match.create({
|
||||||
data: {
|
data: {
|
||||||
id: parsed.matchId,
|
id: parsed.matchId,
|
||||||
@ -106,12 +158,18 @@ export async function parseAndStoreDemo(
|
|||||||
: demoPath.endsWith('_competitive.dem')
|
: demoPath.endsWith('_competitive.dem')
|
||||||
? 'competitive'
|
? 'competitive'
|
||||||
: 'community',
|
: 'community',
|
||||||
scoreA: parsed.meta.scoreCT,
|
scoreA: parsed.meta.teamA?.score,
|
||||||
scoreB: parsed.meta.scoreT,
|
scoreB: parsed.meta.teamB?.score,
|
||||||
winnerTeam: parsed.meta.winnerTeam ?? null,
|
winnerTeam: parsed.meta.winnerTeam ?? null,
|
||||||
roundCount: parsed.meta.roundCount ?? null,
|
roundCount: parsed.meta.roundCount ?? null,
|
||||||
roundHistory: parsed.meta.roundHistory ?? undefined,
|
roundHistory: parsed.meta.roundHistory ?? undefined,
|
||||||
demoDate: parsed.meta.demoDate ?? null,
|
demoDate: parsed.meta.demoDate ?? null,
|
||||||
|
teamAUsers: {
|
||||||
|
connect: teamAIds.map(steamId => ({ steamId })),
|
||||||
|
},
|
||||||
|
teamBUsers: {
|
||||||
|
connect: teamBIds.map(steamId => ({ steamId })),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,50 +183,11 @@ export async function parseAndStoreDemo(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const player of parsed.meta.players) {
|
for (const player of allPlayers) {
|
||||||
let playerUser = await prisma.user.findUnique({
|
|
||||||
where: { steamId: player.steamId },
|
|
||||||
});
|
|
||||||
|
|
||||||
let steamProfile = null;
|
|
||||||
if (!playerUser?.name || !playerUser?.avatar) {
|
|
||||||
steamProfile = await fetchSteamProfile(player.steamId).catch(() => null);
|
|
||||||
await delay(5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPremier = path.basename(actualDemoPath).toLowerCase().endsWith('_premier.dem');
|
|
||||||
|
|
||||||
const updatedFields: Partial<{ name: string; avatar: string; premierRank: number }> = {};
|
|
||||||
|
|
||||||
if (!playerUser) {
|
|
||||||
await prisma.user.create({
|
|
||||||
data: {
|
|
||||||
steamId: player.steamId,
|
|
||||||
name: steamProfile?.name ?? player.name,
|
|
||||||
avatar: steamProfile?.avatar ?? undefined,
|
|
||||||
premierRank: isPremier ? player.rankNew ?? undefined : undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (steamProfile?.name && playerUser.name !== steamProfile.name) {
|
|
||||||
updatedFields.name = steamProfile.name;
|
|
||||||
}
|
|
||||||
if (steamProfile?.avatar && playerUser.avatar !== steamProfile.avatar) {
|
|
||||||
updatedFields.avatar = steamProfile.avatar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(updatedFields).length > 0) {
|
|
||||||
await prisma.user.update({
|
|
||||||
where: { steamId: player.steamId },
|
|
||||||
data: updatedFields,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const teamId =
|
const teamId =
|
||||||
parsed.meta.teamCT && player.team === 'CT'
|
match.teamAId && parsed.meta.teamA?.players.some(p => p.steamId === player.steamId)
|
||||||
? match.teamAId
|
? match.teamAId
|
||||||
: parsed.meta.teamT && player.team === 'T'
|
: match.teamBId && parsed.meta.teamB?.players.some(p => p.steamId === player.steamId)
|
||||||
? match.teamBId
|
? match.teamBId
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
@ -185,7 +204,7 @@ export async function parseAndStoreDemo(
|
|||||||
matchId_steamId: {
|
matchId_steamId: {
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
steamId: player.steamId,
|
steamId: player.steamId,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
kills: player.kills,
|
kills: player.kills,
|
||||||
@ -261,6 +280,8 @@ async function parseDemoViaGo(filePath: string, shareCode: string): Promise<Demo
|
|||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(output);
|
const parsed = JSON.parse(output);
|
||||||
|
console.log(parsed.teamA.players);
|
||||||
|
console.log(parsed.teamB.players);
|
||||||
resolve({
|
resolve({
|
||||||
matchId,
|
matchId,
|
||||||
map: parsed.map,
|
map: parsed.map,
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
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/sse-server-client.js';
|
import { sendServerSSEMessage } from '../app/lib/sse-server-client.js';
|
||||||
import { decrypt } from '../app/lib/crypto.js';
|
import { decrypt } from '../app/lib/crypto.js';
|
||||||
import { encodeMatch, decodeMatchShareCode } 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';
|
import { getNextShareCodeFromAPI } from './getNextShareCodeFromAPI.js';
|
||||||
import { updatePremierRanksForUser } from './updatePremierRanks';
|
import { updatePremierRanksForUser } from './updatePremierRanks';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
|
||||||
let isRunning = false;
|
let isRunning = false;
|
||||||
|
|
||||||
@ -64,7 +67,7 @@ async function runMatchCheck() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'expired-sharecode',
|
type: 'expired-sharecode',
|
||||||
targetUserIds: [user.steamId],
|
targetUserIds: [user.steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
@ -117,6 +120,22 @@ async function runMatchCheck() {
|
|||||||
|
|
||||||
const shareCode = encodeMatch(matchInfo);
|
const shareCode = encodeMatch(matchInfo);
|
||||||
|
|
||||||
|
const expectedFilename = `${matchInfo.matchId}.dem`;
|
||||||
|
const expectedFilePath = path.join(process.cwd(), 'demos', expectedFilename);
|
||||||
|
|
||||||
|
if (fs.existsSync(expectedFilePath)) {
|
||||||
|
log(`[${user.steamId}] 📁 Match ${matchInfo.matchId} wurde bereits als Datei gespeichert – übersprungen`);
|
||||||
|
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { steamId: user.steamId },
|
||||||
|
data: { lastKnownShareCode: nextShareCode },
|
||||||
|
});
|
||||||
|
|
||||||
|
latestKnownCode = nextShareCode;
|
||||||
|
nextShareCode = await getNextShareCodeFromAPI(user.steamId, decryptedAuthCode, latestKnownCode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await runDownloaderForUser({
|
const result = await runDownloaderForUser({
|
||||||
...user,
|
...user,
|
||||||
lastKnownShareCode: shareCode,
|
lastKnownShareCode: shareCode,
|
||||||
@ -170,7 +189,7 @@ async function runMatchCheck() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendServerWebSocketMessage({
|
await sendServerSSEMessage({
|
||||||
type: 'new-cs2-match',
|
type: 'new-cs2-match',
|
||||||
targetUserIds: [user.steamId],
|
targetUserIds: [user.steamId],
|
||||||
message: notification.message,
|
message: notification.message,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import path from 'path';
|
|||||||
import { Match, User } from '@/generated/prisma';
|
import { Match, User } from '@/generated/prisma';
|
||||||
import { parseAndStoreDemo } from './parseAndStoreDemo';
|
import { parseAndStoreDemo } from './parseAndStoreDemo';
|
||||||
import { log } from '../../scripts/cs2-cron-runner.js';
|
import { log } from '../../scripts/cs2-cron-runner.js';
|
||||||
|
import { prisma } from '../app/lib/prisma.js';
|
||||||
|
|
||||||
export async function runDownloaderForUser(user: User): Promise<{
|
export async function runDownloaderForUser(user: User): Promise<{
|
||||||
newMatches: Match[];
|
newMatches: Match[];
|
||||||
@ -37,7 +38,19 @@ export async function runDownloaderForUser(user: User): Promise<{
|
|||||||
return { newMatches: [], latestShareCode: shareCode };
|
return { newMatches: [], latestShareCode: shareCode };
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`[${steamId}] 📂 Analysiere: ${path.basename(demoPath)}`);
|
const filename = path.basename(demoPath);
|
||||||
|
const matchId = filename.replace(/\.dem$/, '');
|
||||||
|
|
||||||
|
const existing = await prisma.match.findUnique({
|
||||||
|
where: { id: matchId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
log(`[${steamId}] 🔁 Match ${matchId} wurde bereits analysiert – übersprungen`, 'info');
|
||||||
|
return { newMatches: [], latestShareCode: shareCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`[${steamId}] 📂 Analysiere: ${filename}`);
|
||||||
|
|
||||||
const absolutePath = path.resolve(__dirname, '../../../cs2-demo-downloader', demoPath);
|
const absolutePath = path.resolve(__dirname, '../../../cs2-demo-downloader', demoPath);
|
||||||
const match = await parseAndStoreDemo(absolutePath, steamId, shareCode);
|
const match = await parseAndStoreDemo(absolutePath, steamId, shareCode);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user