feat(auth): implement token rotation and session management for persistent Android login
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 5m22s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m12s

This commit is contained in:
Torsten Schulz (local)
2026-05-27 18:55:22 +02:00
parent cd025b1f92
commit 1710c9349d
3 changed files with 84 additions and 14 deletions

View File

@@ -403,6 +403,7 @@ const isEditing = ref(false)
const editingIndex = ref(-1)
const formData = ref({ mannschaft: '', liga: '', staffelleiter: '', telefon: '', heimspieltag: '', spielsystem: '', mannschaftsfuehrer: '', spielerListe: [], weitere_informationen_link: '', letzte_aktualisierung: '' })
const moveTargetBySpielerId = ref({})
const initialMoveTargetBySpielerId = ref({})
const pendingSpielerNamesByTeamIndex = ref({})
function nowIsoDate() { return new Date().toISOString().split('T')[0] }
@@ -449,7 +450,7 @@ async function loadSeasons() {
if (!selectedSeason.value || !seasons.value.includes(selectedSeason.value)) {
selectedSeason.value = result.defaultSeason || seasons.value[0]
}
} catch (_error) {
} catch {
if (!seasons.value.length) seasons.value = ['']
if (!selectedSeason.value) selectedSeason.value = seasons.value[0] || ''
}
@@ -461,7 +462,7 @@ const mannschaftenSelectOptions = computed(() => {
return [...new Set([current, ...names])].filter(Boolean)
})
function resetSpielerDraftState() { moveTargetBySpielerId.value = {}; pendingSpielerNamesByTeamIndex.value = {} }
function resetSpielerDraftState() { moveTargetBySpielerId.value = {}; initialMoveTargetBySpielerId.value = {}; pendingSpielerNamesByTeamIndex.value = {} }
function getPendingSpielerNamesForTeamIndex(teamIndex) {
if (pendingSpielerNamesByTeamIndex.value[teamIndex]) return pendingSpielerNamesByTeamIndex.value[teamIndex]
const existing = mannschaften.value[teamIndex]; const list = existing ? getSpielerListe(existing) : []
@@ -497,29 +498,60 @@ const openEditModal = (mannschaft, index) => {
formData.value = { mannschaft: mannschaft.mannschaft || '', liga: mannschaft.liga || '', staffelleiter: mannschaft.staffelleiter || '', telefon: mannschaft.telefon || '', heimspieltag: mannschaft.heimspieltag || '', spielsystem: mannschaft.spielsystem || '', mannschaftsfuehrer: mannschaft.mannschaftsfuehrer || '', spielerListe: parseSpielerString(mannschaft.spieler || ''), weitere_informationen_link: mannschaft.weitere_informationen_link || '', letzte_aktualisierung: mannschaft.letzte_aktualisierung || nowIsoDate() }
isEditing.value = true; editingIndex.value = index; showModal.value = true; errorMessage.value = ''; resetSpielerDraftState()
const currentTeam = (formData.value.mannschaft || '').trim()
for (const s of formData.value.spielerListe) { moveTargetBySpielerId.value[s.id] = currentTeam }
for (const s of formData.value.spielerListe) {
moveTargetBySpielerId.value[s.id] = currentTeam
initialMoveTargetBySpielerId.value[s.id] = currentTeam
}
}
const addSpieler = () => {
const item = newSpielerItem('')
const currentTeam = (formData.value.mannschaft || '').trim()
formData.value.spielerListe.push(item)
moveTargetBySpielerId.value[item.id] = currentTeam
initialMoveTargetBySpielerId.value[item.id] = currentTeam
}
const removeSpieler = (id) => {
const idx = formData.value.spielerListe.findIndex(s => s.id === id)
if (idx === -1) return
formData.value.spielerListe.splice(idx, 1)
delete moveTargetBySpielerId.value[id]
delete initialMoveTargetBySpielerId.value[id]
}
const addSpieler = () => { const item = newSpielerItem(''); formData.value.spielerListe.push(item); moveTargetBySpielerId.value[item.id] = (formData.value.mannschaft || '').trim() }
const removeSpieler = (id) => { const idx = formData.value.spielerListe.findIndex(s => s.id === id); if (idx === -1) return; formData.value.spielerListe.splice(idx, 1); if (moveTargetBySpielerId.value[id]) delete moveTargetBySpielerId.value[id] }
const moveSpielerUp = (index) => { if (index <= 0) return; const arr = formData.value.spielerListe; const item = arr[index]; arr.splice(index, 1); arr.splice(index - 1, 0, item) }
const moveSpielerDown = (index) => { const arr = formData.value.spielerListe; if (index < 0 || index >= arr.length - 1) return; const item = arr[index]; arr.splice(index, 1); arr.splice(index + 1, 0, item) }
const canMoveSpieler = (id) => { const t = (moveTargetBySpielerId.value[id] || '').trim(); const c = (formData.value.mannschaft || '').trim(); return Boolean(t) && Boolean(c) && t !== c }
const canMoveSpieler = (id) => {
const target = (moveTargetBySpielerId.value[id] || '').trim()
const initialTarget = (initialMoveTargetBySpielerId.value[id] || '').trim()
return Boolean(target) && Boolean(initialTarget) && target !== initialTarget
}
const moveSpielerToMannschaft = (spielerId) => {
if (!isEditing.value || editingIndex.value < 0) return
const targetName = (moveTargetBySpielerId.value[spielerId] || '').trim(); if (!targetName) return
if (!isEditing.value || editingIndex.value < 0) return false
const targetName = (moveTargetBySpielerId.value[spielerId] || '').trim(); if (!targetName) return false
const targetIndex = mannschaften.value.findIndex((m, idx) => { if (idx === editingIndex.value) return false; return (m?.mannschaft || '').trim() === targetName })
if (targetIndex === -1) { errorMessage.value = 'Ziel-Mannschaft nicht gefunden.'; return }
const idx = formData.value.spielerListe.findIndex(s => s.id === spielerId); if (idx === -1) return
const spielerName = (formData.value.spielerListe[idx]?.name || '').trim(); if (!spielerName) { errorMessage.value = 'Bitte zuerst einen Spielernamen eintragen.'; return }
if (targetIndex === -1) { errorMessage.value = 'Ziel-Mannschaft nicht gefunden.'; return false }
const idx = formData.value.spielerListe.findIndex(s => s.id === spielerId); if (idx === -1) return false
const spielerName = (formData.value.spielerListe[idx]?.name || '').trim(); if (!spielerName) { errorMessage.value = 'Bitte zuerst einen Spielernamen eintragen.'; return false }
formData.value.spielerListe.splice(idx, 1)
const pendingList = getPendingSpielerNamesForTeamIndex(targetIndex); pendingList.push(spielerName)
delete moveTargetBySpielerId.value[spielerId]
delete initialMoveTargetBySpielerId.value[spielerId]
return true
}
const applySelectedSpielerTransfers = () => {
if (!isEditing.value || editingIndex.value < 0) return true
const pendingIds = formData.value.spielerListe
.filter(spieler => canMoveSpieler(spieler.id))
.map(spieler => spieler.id)
return pendingIds.every(spielerId => moveSpielerToMannschaft(spielerId))
}
const saveMannschaft = async () => {
isSaving.value = true; errorMessage.value = ''
try {
if (!applySelectedSpielerTransfers()) return
const spielerString = serializeSpielerList(formData.value.spielerListe)
const updated = { mannschaft: formData.value.mannschaft || '', liga: formData.value.liga || '', staffelleiter: formData.value.staffelleiter || '', telefon: formData.value.telefon || '', heimspieltag: formData.value.heimspieltag || '', spielsystem: formData.value.spielsystem || '', mannschaftsfuehrer: formData.value.mannschaftsfuehrer || '', spieler: spielerString, weitere_informationen_link: formData.value.weitere_informationen_link || '', letzte_aktualisierung: formData.value.letzte_aktualisierung || nowIsoDate() }
if (isEditing.value && editingIndex.value >= 0) { mannschaften.value[editingIndex.value] = { ...updated } } else { mannschaften.value.push({ ...updated }) }