This commit is contained in:
Linrador 2025-10-08 23:41:12 +02:00
parent caaed1f71e
commit ba69e99120
16 changed files with 499 additions and 32 deletions

View File

@ -437,9 +437,21 @@ model MatchReady {
model ServerConfig {
id String @id
serverIp String
serverPassword String? // ⬅️ neu
serverPassword String?
pterodactylServerId String
pterodactylServerApiKey String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// ───── Live / GameBanner ─────
activeMatchId String?
activeMapKey String?
activeMapLabel String?
activeMapBg String?
activeParticipants String[] // steamIds
activeSince DateTime?
bannerExpiresAt DateTime?
@@index([activeMatchId])
}

View File

@ -274,25 +274,29 @@ export default function MiniPlayerCard({
alt={u.name || 'Avatar'}
className="h-12 w-12 rounded-full ring-1 ring-white/15"
/>
<div className="min-w-0">
{/* Name */}
<div className="truncate text-sm font-semibold">
{u.name ?? 'Unbekannt'}
</div>
{/* darunter: Premier + Faceit + Ban */}
<div className="mt-1 flex items-center gap-2">
<PremierRankBadge rank={u.premierRank ?? 0} />
{faceit.nickname && <FaceitLevelImage elo={faceit.elo ?? 0} className="-ml-0.5" />}
{/* Name + BAN/VAC direkt daneben (wie MatchDetails) */}
<div className="flex items-center gap-2 min-w-0">
<span className="truncate text-sm font-semibold">
{u.name ?? 'Unbekannt'}
</span>
{isBanned && (
<span
title={banTooltip}
className="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-bold"
style={{ background: hasVac ? 'rgba(220,38,38,.9)' : 'rgba(234,88,12,.9)' }}
className="ml-1 shrink-0 inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-bold bg-red-600 text-white"
aria-label={hasVac ? 'Dieser Spieler hat einen VAC-Ban' : 'Dieser Spieler ist gebannt'}
>
{hasVac ? 'VAC' : 'BAN'}
</span>
)}
</div>
{/* darunter: Premier + Faceit (unverändert) */}
<div className="mt-2 flex items-center gap-2">
<PremierRankBadge rank={u.premierRank ?? 0} />
{faceit.nickname && <FaceitLevelImage elo={faceit.elo ?? 0} className="-ml-0.5" />}
</div>
</div>
</div>

View File

@ -0,0 +1,53 @@
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
export const dynamic = 'force-dynamic'
export const runtime = 'nodejs'
export async function GET() {
try {
const cfg = await prisma.serverConfig.findUnique({
where: { id: 'default' },
select: {
activeMatchId: true,
activeMapKey: true,
activeMapLabel: true,
activeMapBg: true,
activeParticipants: true,
activeSince: true,
bannerExpiresAt: true,
updatedAt: true,
},
})
return NextResponse.json({ ok: true, data: cfg ?? null }, { headers: { 'Cache-Control': 'no-store' }})
} catch (e) {
console.error('[server/live][GET]', e)
return NextResponse.json({ ok: false }, { status: 500 })
}
}
export async function POST(req: Request) {
// Optional: Banner leeren (z.B. wenn Match beendet)
try {
const body = await req.json().catch(() => ({}))
if (body?.action === 'clear') {
await prisma.serverConfig.update({
where: { id: 'default' },
data: {
activeMatchId: null,
activeMapKey: null,
activeMapLabel: null,
activeMapBg: null,
activeParticipants: [],
activeSince: null,
bannerExpiresAt: null,
},
})
return NextResponse.json({ ok: true })
}
return NextResponse.json({ ok: false, message: 'Unsupported action' }, { status: 400 })
} catch (e) {
console.error('[server/live][POST]', e)
return NextResponse.json({ ok: false }, { status: 500 })
}
}

View File

@ -529,6 +529,47 @@ async function exportMatchToSftpDirect(match: any, vote: any) {
}
}
async function writeLiveStateToServerConfig(match: any, vote: any, mapVisuals: Record<string, any>) {
try {
// gewählte Maps (PICK/DECIDER), erste Map extrahieren
const chosen = deriveChosenSteps(vote)
const first = chosen[0]
const key = first?.map ?? null
const label = key ? (mapVisuals?.[key]?.label ?? key) : null
const bg = key ? (mapVisuals?.[key]?.bg ?? `/assets/img/maps/${key}/1.jpg`) : null
const participants = collectParticipants(match)
// Wir gehen davon aus, dass es "default" gibt.
await prisma.serverConfig.update({
where: { id: 'default' },
data: {
activeMatchId: match.id,
activeMapKey : key,
activeMapLabel: label,
activeMapBg : bg,
activeParticipants: participants,
activeSince : new Date(),
// z.B. auf 2h Sichtbarkeit begrenzen (optional)
bannerExpiresAt: new Date(Date.now() + 2 * 60 * 60 * 1000),
},
})
} catch (e) {
console.warn('[mapvote] writeLiveStateToServerConfig failed:', e)
}
}
/** DRY: Wird in jedem "locked"-Pfad aufgerufen */
async function afterVoteLocked(match: any, vote: any, mapVisuals: Record<string, any>) {
// 1) Spieler für dieses Match festschreiben
await persistMatchPlayers(match)
// 2) Live-State für Banner speichern
await writeLiveStateToServerConfig(match, vote, mapVisuals)
// 3) Serverexport + Matchzy-Load
await exportMatchToSftpDirect(match, vote)
}
/* ---------- kleine Helfer für match-ready Payload ---------- */
function deriveChosenSteps(vote: any) {
@ -654,6 +695,8 @@ export async function POST(req: NextRequest, { params }: { params: { matchId: st
firstMap: { key, label, bg },
participants,
});
await afterVoteLocked(match, updated, mapVisuals)
// Export serverseitig
await exportMatchToSftpDirect(match, updated)
@ -718,6 +761,8 @@ export async function POST(req: NextRequest, { params }: { params: { matchId: st
participants,
});
await afterVoteLocked(match, updated, mapVisuals)
// Export serverseitig
await exportMatchToSftpDirect(match, updated)
}
@ -819,6 +864,8 @@ export async function POST(req: NextRequest, { params }: { params: { matchId: st
participants,
});
await afterVoteLocked(match, updated, mapVisuals)
await exportMatchToSftpDirect(match, updated)
}

File diff suppressed because one or more lines are too long

View File

@ -351,7 +351,14 @@ exports.Prisma.ServerConfigScalarFieldEnum = {
pterodactylServerId: 'pterodactylServerId',
pterodactylServerApiKey: 'pterodactylServerApiKey',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
updatedAt: 'updatedAt',
activeMatchId: 'activeMatchId',
activeMapKey: 'activeMapKey',
activeMapLabel: 'activeMapLabel',
activeMapBg: 'activeMapBg',
activeParticipants: 'activeParticipants',
activeSince: 'activeSince',
bannerExpiresAt: 'bannerExpiresAt'
};
exports.Prisma.SortOrder = {

View File

@ -21476,6 +21476,12 @@ export namespace Prisma {
pterodactylServerApiKey: string | null
createdAt: Date | null
updatedAt: Date | null
activeMatchId: string | null
activeMapKey: string | null
activeMapLabel: string | null
activeMapBg: string | null
activeSince: Date | null
bannerExpiresAt: Date | null
}
export type ServerConfigMaxAggregateOutputType = {
@ -21486,6 +21492,12 @@ export namespace Prisma {
pterodactylServerApiKey: string | null
createdAt: Date | null
updatedAt: Date | null
activeMatchId: string | null
activeMapKey: string | null
activeMapLabel: string | null
activeMapBg: string | null
activeSince: Date | null
bannerExpiresAt: Date | null
}
export type ServerConfigCountAggregateOutputType = {
@ -21496,6 +21508,13 @@ export namespace Prisma {
pterodactylServerApiKey: number
createdAt: number
updatedAt: number
activeMatchId: number
activeMapKey: number
activeMapLabel: number
activeMapBg: number
activeParticipants: number
activeSince: number
bannerExpiresAt: number
_all: number
}
@ -21508,6 +21527,12 @@ export namespace Prisma {
pterodactylServerApiKey?: true
createdAt?: true
updatedAt?: true
activeMatchId?: true
activeMapKey?: true
activeMapLabel?: true
activeMapBg?: true
activeSince?: true
bannerExpiresAt?: true
}
export type ServerConfigMaxAggregateInputType = {
@ -21518,6 +21543,12 @@ export namespace Prisma {
pterodactylServerApiKey?: true
createdAt?: true
updatedAt?: true
activeMatchId?: true
activeMapKey?: true
activeMapLabel?: true
activeMapBg?: true
activeSince?: true
bannerExpiresAt?: true
}
export type ServerConfigCountAggregateInputType = {
@ -21528,6 +21559,13 @@ export namespace Prisma {
pterodactylServerApiKey?: true
createdAt?: true
updatedAt?: true
activeMatchId?: true
activeMapKey?: true
activeMapLabel?: true
activeMapBg?: true
activeParticipants?: true
activeSince?: true
bannerExpiresAt?: true
_all?: true
}
@ -21611,6 +21649,13 @@ export namespace Prisma {
pterodactylServerApiKey: string
createdAt: Date
updatedAt: Date
activeMatchId: string | null
activeMapKey: string | null
activeMapLabel: string | null
activeMapBg: string | null
activeParticipants: string[]
activeSince: Date | null
bannerExpiresAt: Date | null
_count: ServerConfigCountAggregateOutputType | null
_min: ServerConfigMinAggregateOutputType | null
_max: ServerConfigMaxAggregateOutputType | null
@ -21638,6 +21683,13 @@ export namespace Prisma {
pterodactylServerApiKey?: boolean
createdAt?: boolean
updatedAt?: boolean
activeMatchId?: boolean
activeMapKey?: boolean
activeMapLabel?: boolean
activeMapBg?: boolean
activeParticipants?: boolean
activeSince?: boolean
bannerExpiresAt?: boolean
}, ExtArgs["result"]["serverConfig"]>
export type ServerConfigSelectCreateManyAndReturn<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
@ -21648,6 +21700,13 @@ export namespace Prisma {
pterodactylServerApiKey?: boolean
createdAt?: boolean
updatedAt?: boolean
activeMatchId?: boolean
activeMapKey?: boolean
activeMapLabel?: boolean
activeMapBg?: boolean
activeParticipants?: boolean
activeSince?: boolean
bannerExpiresAt?: boolean
}, ExtArgs["result"]["serverConfig"]>
export type ServerConfigSelectUpdateManyAndReturn<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetSelect<{
@ -21658,6 +21717,13 @@ export namespace Prisma {
pterodactylServerApiKey?: boolean
createdAt?: boolean
updatedAt?: boolean
activeMatchId?: boolean
activeMapKey?: boolean
activeMapLabel?: boolean
activeMapBg?: boolean
activeParticipants?: boolean
activeSince?: boolean
bannerExpiresAt?: boolean
}, ExtArgs["result"]["serverConfig"]>
export type ServerConfigSelectScalar = {
@ -21668,9 +21734,16 @@ export namespace Prisma {
pterodactylServerApiKey?: boolean
createdAt?: boolean
updatedAt?: boolean
activeMatchId?: boolean
activeMapKey?: boolean
activeMapLabel?: boolean
activeMapBg?: boolean
activeParticipants?: boolean
activeSince?: boolean
bannerExpiresAt?: boolean
}
export type ServerConfigOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"id" | "serverIp" | "serverPassword" | "pterodactylServerId" | "pterodactylServerApiKey" | "createdAt" | "updatedAt", ExtArgs["result"]["serverConfig"]>
export type ServerConfigOmit<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = $Extensions.GetOmit<"id" | "serverIp" | "serverPassword" | "pterodactylServerId" | "pterodactylServerApiKey" | "createdAt" | "updatedAt" | "activeMatchId" | "activeMapKey" | "activeMapLabel" | "activeMapBg" | "activeParticipants" | "activeSince" | "bannerExpiresAt", ExtArgs["result"]["serverConfig"]>
export type $ServerConfigPayload<ExtArgs extends $Extensions.InternalArgs = $Extensions.DefaultArgs> = {
name: "ServerConfig"
@ -21683,6 +21756,13 @@ export namespace Prisma {
pterodactylServerApiKey: string
createdAt: Date
updatedAt: Date
activeMatchId: string | null
activeMapKey: string | null
activeMapLabel: string | null
activeMapBg: string | null
activeParticipants: string[]
activeSince: Date | null
bannerExpiresAt: Date | null
}, ExtArgs["result"]["serverConfig"]>
composites: {}
}
@ -22113,6 +22193,13 @@ export namespace Prisma {
readonly pterodactylServerApiKey: FieldRef<"ServerConfig", 'String'>
readonly createdAt: FieldRef<"ServerConfig", 'DateTime'>
readonly updatedAt: FieldRef<"ServerConfig", 'DateTime'>
readonly activeMatchId: FieldRef<"ServerConfig", 'String'>
readonly activeMapKey: FieldRef<"ServerConfig", 'String'>
readonly activeMapLabel: FieldRef<"ServerConfig", 'String'>
readonly activeMapBg: FieldRef<"ServerConfig", 'String'>
readonly activeParticipants: FieldRef<"ServerConfig", 'String[]'>
readonly activeSince: FieldRef<"ServerConfig", 'DateTime'>
readonly bannerExpiresAt: FieldRef<"ServerConfig", 'DateTime'>
}
@ -22769,7 +22856,14 @@ export namespace Prisma {
pterodactylServerId: 'pterodactylServerId',
pterodactylServerApiKey: 'pterodactylServerApiKey',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
updatedAt: 'updatedAt',
activeMatchId: 'activeMatchId',
activeMapKey: 'activeMapKey',
activeMapLabel: 'activeMapLabel',
activeMapBg: 'activeMapBg',
activeParticipants: 'activeParticipants',
activeSince: 'activeSince',
bannerExpiresAt: 'bannerExpiresAt'
};
export type ServerConfigScalarFieldEnum = (typeof ServerConfigScalarFieldEnum)[keyof typeof ServerConfigScalarFieldEnum]
@ -24484,6 +24578,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFilter<"ServerConfig"> | string
createdAt?: DateTimeFilter<"ServerConfig"> | Date | string
updatedAt?: DateTimeFilter<"ServerConfig"> | Date | string
activeMatchId?: StringNullableFilter<"ServerConfig"> | string | null
activeMapKey?: StringNullableFilter<"ServerConfig"> | string | null
activeMapLabel?: StringNullableFilter<"ServerConfig"> | string | null
activeMapBg?: StringNullableFilter<"ServerConfig"> | string | null
activeParticipants?: StringNullableListFilter<"ServerConfig">
activeSince?: DateTimeNullableFilter<"ServerConfig"> | Date | string | null
bannerExpiresAt?: DateTimeNullableFilter<"ServerConfig"> | Date | string | null
}
export type ServerConfigOrderByWithRelationInput = {
@ -24494,6 +24595,13 @@ export namespace Prisma {
pterodactylServerApiKey?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
activeMatchId?: SortOrderInput | SortOrder
activeMapKey?: SortOrderInput | SortOrder
activeMapLabel?: SortOrderInput | SortOrder
activeMapBg?: SortOrderInput | SortOrder
activeParticipants?: SortOrder
activeSince?: SortOrderInput | SortOrder
bannerExpiresAt?: SortOrderInput | SortOrder
}
export type ServerConfigWhereUniqueInput = Prisma.AtLeast<{
@ -24507,6 +24615,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFilter<"ServerConfig"> | string
createdAt?: DateTimeFilter<"ServerConfig"> | Date | string
updatedAt?: DateTimeFilter<"ServerConfig"> | Date | string
activeMatchId?: StringNullableFilter<"ServerConfig"> | string | null
activeMapKey?: StringNullableFilter<"ServerConfig"> | string | null
activeMapLabel?: StringNullableFilter<"ServerConfig"> | string | null
activeMapBg?: StringNullableFilter<"ServerConfig"> | string | null
activeParticipants?: StringNullableListFilter<"ServerConfig">
activeSince?: DateTimeNullableFilter<"ServerConfig"> | Date | string | null
bannerExpiresAt?: DateTimeNullableFilter<"ServerConfig"> | Date | string | null
}, "id">
export type ServerConfigOrderByWithAggregationInput = {
@ -24517,6 +24632,13 @@ export namespace Prisma {
pterodactylServerApiKey?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
activeMatchId?: SortOrderInput | SortOrder
activeMapKey?: SortOrderInput | SortOrder
activeMapLabel?: SortOrderInput | SortOrder
activeMapBg?: SortOrderInput | SortOrder
activeParticipants?: SortOrder
activeSince?: SortOrderInput | SortOrder
bannerExpiresAt?: SortOrderInput | SortOrder
_count?: ServerConfigCountOrderByAggregateInput
_max?: ServerConfigMaxOrderByAggregateInput
_min?: ServerConfigMinOrderByAggregateInput
@ -24533,6 +24655,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringWithAggregatesFilter<"ServerConfig"> | string
createdAt?: DateTimeWithAggregatesFilter<"ServerConfig"> | Date | string
updatedAt?: DateTimeWithAggregatesFilter<"ServerConfig"> | Date | string
activeMatchId?: StringNullableWithAggregatesFilter<"ServerConfig"> | string | null
activeMapKey?: StringNullableWithAggregatesFilter<"ServerConfig"> | string | null
activeMapLabel?: StringNullableWithAggregatesFilter<"ServerConfig"> | string | null
activeMapBg?: StringNullableWithAggregatesFilter<"ServerConfig"> | string | null
activeParticipants?: StringNullableListFilter<"ServerConfig">
activeSince?: DateTimeNullableWithAggregatesFilter<"ServerConfig"> | Date | string | null
bannerExpiresAt?: DateTimeNullableWithAggregatesFilter<"ServerConfig"> | Date | string | null
}
export type UserCreateInput = {
@ -26208,6 +26337,13 @@ export namespace Prisma {
pterodactylServerApiKey: string
createdAt?: Date | string
updatedAt?: Date | string
activeMatchId?: string | null
activeMapKey?: string | null
activeMapLabel?: string | null
activeMapBg?: string | null
activeParticipants?: ServerConfigCreateactiveParticipantsInput | string[]
activeSince?: Date | string | null
bannerExpiresAt?: Date | string | null
}
export type ServerConfigUncheckedCreateInput = {
@ -26218,6 +26354,13 @@ export namespace Prisma {
pterodactylServerApiKey: string
createdAt?: Date | string
updatedAt?: Date | string
activeMatchId?: string | null
activeMapKey?: string | null
activeMapLabel?: string | null
activeMapBg?: string | null
activeParticipants?: ServerConfigCreateactiveParticipantsInput | string[]
activeSince?: Date | string | null
bannerExpiresAt?: Date | string | null
}
export type ServerConfigUpdateInput = {
@ -26228,6 +26371,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFieldUpdateOperationsInput | string
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
activeMatchId?: NullableStringFieldUpdateOperationsInput | string | null
activeMapKey?: NullableStringFieldUpdateOperationsInput | string | null
activeMapLabel?: NullableStringFieldUpdateOperationsInput | string | null
activeMapBg?: NullableStringFieldUpdateOperationsInput | string | null
activeParticipants?: ServerConfigUpdateactiveParticipantsInput | string[]
activeSince?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
bannerExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
}
export type ServerConfigUncheckedUpdateInput = {
@ -26238,6 +26388,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFieldUpdateOperationsInput | string
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
activeMatchId?: NullableStringFieldUpdateOperationsInput | string | null
activeMapKey?: NullableStringFieldUpdateOperationsInput | string | null
activeMapLabel?: NullableStringFieldUpdateOperationsInput | string | null
activeMapBg?: NullableStringFieldUpdateOperationsInput | string | null
activeParticipants?: ServerConfigUpdateactiveParticipantsInput | string[]
activeSince?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
bannerExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
}
export type ServerConfigCreateManyInput = {
@ -26248,6 +26405,13 @@ export namespace Prisma {
pterodactylServerApiKey: string
createdAt?: Date | string
updatedAt?: Date | string
activeMatchId?: string | null
activeMapKey?: string | null
activeMapLabel?: string | null
activeMapBg?: string | null
activeParticipants?: ServerConfigCreateactiveParticipantsInput | string[]
activeSince?: Date | string | null
bannerExpiresAt?: Date | string | null
}
export type ServerConfigUpdateManyMutationInput = {
@ -26258,6 +26422,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFieldUpdateOperationsInput | string
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
activeMatchId?: NullableStringFieldUpdateOperationsInput | string | null
activeMapKey?: NullableStringFieldUpdateOperationsInput | string | null
activeMapLabel?: NullableStringFieldUpdateOperationsInput | string | null
activeMapBg?: NullableStringFieldUpdateOperationsInput | string | null
activeParticipants?: ServerConfigUpdateactiveParticipantsInput | string[]
activeSince?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
bannerExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
}
export type ServerConfigUncheckedUpdateManyInput = {
@ -26268,6 +26439,13 @@ export namespace Prisma {
pterodactylServerApiKey?: StringFieldUpdateOperationsInput | string
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
activeMatchId?: NullableStringFieldUpdateOperationsInput | string | null
activeMapKey?: NullableStringFieldUpdateOperationsInput | string | null
activeMapLabel?: NullableStringFieldUpdateOperationsInput | string | null
activeMapBg?: NullableStringFieldUpdateOperationsInput | string | null
activeParticipants?: ServerConfigUpdateactiveParticipantsInput | string[]
activeSince?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
bannerExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
}
export type StringFilter<$PrismaModel = never> = {
@ -27701,6 +27879,13 @@ export namespace Prisma {
pterodactylServerApiKey?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
activeMatchId?: SortOrder
activeMapKey?: SortOrder
activeMapLabel?: SortOrder
activeMapBg?: SortOrder
activeParticipants?: SortOrder
activeSince?: SortOrder
bannerExpiresAt?: SortOrder
}
export type ServerConfigMaxOrderByAggregateInput = {
@ -27711,6 +27896,12 @@ export namespace Prisma {
pterodactylServerApiKey?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
activeMatchId?: SortOrder
activeMapKey?: SortOrder
activeMapLabel?: SortOrder
activeMapBg?: SortOrder
activeSince?: SortOrder
bannerExpiresAt?: SortOrder
}
export type ServerConfigMinOrderByAggregateInput = {
@ -27721,6 +27912,12 @@ export namespace Prisma {
pterodactylServerApiKey?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
activeMatchId?: SortOrder
activeMapKey?: SortOrder
activeMapLabel?: SortOrder
activeMapBg?: SortOrder
activeSince?: SortOrder
bannerExpiresAt?: SortOrder
}
export type TeamCreateNestedOneWithoutMembersInput = {
@ -29524,6 +29721,15 @@ export namespace Prisma {
update?: XOR<XOR<UserUpdateToOneWithWhereWithoutReadyAcceptancesInput, UserUpdateWithoutReadyAcceptancesInput>, UserUncheckedUpdateWithoutReadyAcceptancesInput>
}
export type ServerConfigCreateactiveParticipantsInput = {
set: string[]
}
export type ServerConfigUpdateactiveParticipantsInput = {
set?: string[]
push?: string | string[]
}
export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | StringFieldRefInput<$PrismaModel>
in?: string[] | ListStringFieldRefInput<$PrismaModel>

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
{
"name": "prisma-client-95b977b21ef98eb4d9fe600b3adfad15a11574a50aec075d54017df21315a04c",
"name": "prisma-client-593c7e86a193b89a0b02a9e811c3a30c125592905a16d5c01f1f9522bc8c8086",
"main": "index.js",
"types": "index.d.ts",
"browser": "default.js",

View File

@ -437,9 +437,20 @@ model MatchReady {
model ServerConfig {
id String @id
serverIp String
serverPassword String? // ⬅️ neu
serverPassword String?
pterodactylServerId String
pterodactylServerApiKey String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// ───── Live / GameBanner ─────
activeMatchId String?
activeMapKey String?
activeMapLabel String?
activeMapBg String?
activeParticipants String[] // steamIds
activeSince DateTime?
bannerExpiresAt DateTime?
@@index([activeMatchId])
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
'use client'
import { create } from 'zustand'
type BannerState = {
visible: boolean
map: { key: string|null, label: string|null, bg: string|null }
participants: string[]
show: (p: { key: string|null, label: string|null, bg: string|null, participants: string[] }) => void
hide: () => void
}
export const useGameBannerStore = create<BannerState>((set) => ({
visible: false,
map: { key: null, label: null, bg: null },
participants: [],
show: ({ key, label, bg, participants }) => set({ visible: true, map: { key, label, bg }, participants }),
hide: () => set({ visible: false, participants: [], map: { key: null, label: null, bg: null } }),
}))

View File

@ -1,19 +1,21 @@
// /src/worker/jobs/processUserMatches.ts
// /src/worker/jobs/matchScannerCron.ts
import cron from 'node-cron';
import { prisma } from "@/lib/prisma";
import { decrypt } from '@/lib/crypto';
import { processUserMatches } from '../tasks/processUserMatchesTask';
import { refreshUserBansTask } from '../tasks/refreshUserBansTask';
import { refreshFaceitProfilesTask } from '../tasks/refreshFaceitProfilesTask'; // ⬅️ NEU
import { log } from '../lib/logger';
let runningMatches = false;
let runningBans = false;
let runningFaceit = false; // ⬅️ NEU
export function startCS2MatchCron() {
log.info('🚀 CS2-CronJob Runner gestartet!');
// Matches z. B. alle 10s (statt jede Sekunde)
// Matches z. B. alle 10s
const jobMatches = cron.schedule('*/10 * * * * *', async () => {
if (runningMatches) return;
runningMatches = true;
@ -31,8 +33,25 @@ export function startCS2MatchCron() {
finally { runningBans = false; }
});
// Faceit-Refresh alle 15 Minuten (anpassbar)
const jobFaceit = cron.schedule('*/15 * * * *', async () => {
if (runningFaceit) return;
runningFaceit = true;
try {
await refreshFaceitProfilesTask({ maxAgeMinutes: 24 * 60, limit: 500 });
} catch (e) {
log.error(e);
} finally {
runningFaceit = false;
}
});
// Initiale Läufe beim Start
refreshUserBansTask().catch(e => log.error(e));
return { jobMatches, jobBans };
refreshFaceitProfilesTask({ maxAgeMinutes: 30 * 24 * 60, limit: 2000 })
.catch(e => log.error(e));
return { jobMatches, jobBans, jobFaceit };
}
async function runMatchCheck() {
@ -44,4 +63,4 @@ async function runMatchCheck() {
const auth = decrypt(user.authCode!);
await processUserMatches(user.steamId, auth, user.lastKnownShareCode!);
}
}
}

View File

@ -0,0 +1,69 @@
// /src/worker/tasks/refreshFaceitProfilesTask.ts
import { prisma } from '@/lib/prisma'
import { syncFaceitProfile } from '@/lib/faceit'
import { log } from '../lib/logger'
type Options = {
/** nur Einträge aktualisieren, deren letzte cs2-Faceit-Stats älter sind als X Minuten */
maxAgeMinutes?: number
/** maximale Anzahl pro Lauf */
limit?: number
}
export async function refreshFaceitProfilesTask(opts: Options = {}): Promise<void> {
const {
maxAgeMinutes = 12 * 60, // 12h
limit = 500,
} = opts
const since = new Date(Date.now() - maxAgeMinutes * 60_000)
// Nutzer auswählen, die (a) noch keine Faceit-Daten haben ODER (b) veraltete cs2-Stats
const candidates = await prisma.user.findMany({
where: {
OR: [
// noch nie gesynct / keine Grunddaten
{ faceitId: null },
{ faceitNickname: null },
{ faceitUrl: null },
// keine cs2-Game-Stats in FaceitGameStat
{
faceitGames: {
none: { game: 'cs2' },
},
},
// cs2-Stats zu alt
{
faceitGames: {
some: {
game: 'cs2',
// falls dein Model kein updatedAt hat, diesen Block entfernen
updatedAt: { lt: since },
},
},
},
],
},
select: { steamId: true },
take: limit,
})
if (candidates.length === 0) {
log.info('[faceit] Keine Kandidaten für Refresh gefunden.')
return
}
log.info(`[faceit] Aktualisiere ${candidates.length} Nutzer…`)
for (const u of candidates) {
try {
await syncFaceitProfile(prisma, u.steamId)
} catch (e) {
log.warn(`[faceit] Fehler bei ${u.steamId}`, e)
}
}
log.info(`[faceit] Aktualisierung abgeschlossen.`)
}