import { computed, onMounted, reactive, ref, watch } from 'vue' import { ApiRequestError, api } from '../../lib/api' import { clearSiteStatusCache } from '../../lib/siteStatus' import type { AdminOperationalSettingsResponse } from '../../types/awards' import type { AdminOperationalSettingsForm, AdminSettingsStatusSummary } from './adminSettingsTypes' const fallbackMaintenanceTitle = 'Sternenpause' const fallbackMaintenanceMessage = 'Die Award-Galaxie wird gerade liebevoll poliert. Schau gleich wieder vorbei.' export function useAdminOperationalSettings() { const operationalLoading = ref(true) const operationalSaving = ref(false) const operationalError = ref('') const operationalSuccess = ref('') const demoPasswordSet = ref(false) const demoManagedByDatabase = ref(false) const demoPasswordInput = ref('') const twitchClientSecretSet = ref(false) const twitchAuthConfigured = ref(false) const twitchAuthManagedByDatabase = ref(false) const twitchClientSecretInput = ref('') const savedOperationalSnapshot = ref('') const operationalForm = reactive({ demoLoginEnabled: false, demoLoginIdentifier: '', demoLoginTwitchUserId: '', demoLoginDisplayName: '', twitchClientId: '', twitchRedirectUri: '', twitchScope: '', sessionIdleTimeoutHours: 3, maintenanceModeEnabled: false, maintenanceTitle: fallbackMaintenanceTitle, maintenanceMessage: fallbackMaintenanceMessage, }) const demoPasswordHint = computed(() => { if (demoPasswordInput.value.trim()) { return 'Dieses neue Passwort wird beim Speichern gesetzt.' } if (demoPasswordSet.value) { return demoManagedByDatabase.value ? 'Passwort ist gesetzt. Leer lassen, wenn es bleiben soll.' : 'Aktuell kommt das Passwort aus der Demo-Config. Beim Speichern wird es in die DB übernommen.' } return 'Noch kein Demo-Passwort gesetzt. Zum Aktivieren bitte ein Passwort vergeben.' }) const demoCredentialsComplete = computed(() => !operationalForm.demoLoginEnabled || ( Boolean(operationalForm.demoLoginIdentifier.trim()) && Boolean(operationalForm.demoLoginTwitchUserId.trim()) && Boolean(operationalForm.demoLoginDisplayName.trim()) && (demoPasswordSet.value || Boolean(demoPasswordInput.value.trim())) ), ) const twitchAuthComplete = computed(() => Boolean(operationalForm.twitchClientId.trim()) && (twitchClientSecretSet.value || Boolean(twitchClientSecretInput.value.trim())) ) const twitchSecretHint = computed(() => { if (twitchClientSecretInput.value.trim()) { return 'Dieses neue Secret wird beim Speichern gesetzt.' } if (twitchClientSecretSet.value) { return 'Client Secret ist gesetzt. Leer lassen, wenn es bleiben soll.' } return 'Noch kein Client Secret gesetzt. Beim ersten Speichern erforderlich.' }) const operationalSummary = computed(() => [ { label: 'Demo Login', value: operationalForm.demoLoginEnabled ? 'Aktiv' : 'Aus', note: operationalForm.demoLoginEnabled ? demoCredentialsComplete.value ? 'Öffentlicher Zugriff ist geschützt.' : 'Aktivierung braucht vollständige Zugangsdaten.' : 'Landingpage ist öffentlich erreichbar.', tone: operationalForm.demoLoginEnabled ? demoCredentialsComplete.value ? 'warning' : 'danger' : 'good', }, { label: 'Session Timeout', value: `${operationalForm.sessionIdleTimeoutHours}h`, note: `Logout nach ${operationalForm.sessionIdleTimeoutHours} Stunden ohne Aktivitaet.`, tone: operationalForm.sessionIdleTimeoutHours >= 3 ? 'good' : 'danger', }, { label: 'Wartungsmodus', value: operationalForm.maintenanceModeEnabled ? 'Aktiv' : 'Aus', note: operationalForm.maintenanceModeEnabled ? 'Besucher sehen die Wartungsseite.' : 'Öffentliche Seiten werden normal ausgeliefert.', tone: operationalForm.maintenanceModeEnabled ? 'warning' : 'good', }, { label: 'Twitch OAuth', value: twitchAuthConfigured.value ? 'Konfiguriert' : 'Fehlt', note: twitchAuthConfigured.value ? twitchAuthManagedByDatabase.value ? 'Quelle: Datenbank' : 'Quelle: App-Konfiguration' : 'Offizieller Twitch Login ist noch nicht aktiv.', tone: twitchAuthConfigured.value ? 'good' : 'warning', }, { label: 'Passwort', value: demoPasswordSet.value ? 'Gesetzt' : 'Fehlt', note: demoManagedByDatabase.value ? 'Quelle: Datenbank' : 'Quelle: App-Konfiguration', tone: demoPasswordSet.value ? 'good' : 'warning', }, ]) const hasUnsavedOperationalChanges = computed(() => Boolean(savedOperationalSnapshot.value) && buildOperationalSnapshot() !== savedOperationalSnapshot.value, ) function applyOperationalSettings(response: AdminOperationalSettingsResponse) { operationalForm.demoLoginEnabled = response.demoLoginEnabled operationalForm.demoLoginIdentifier = response.demoLoginEmail operationalForm.demoLoginTwitchUserId = response.demoLoginTwitchUserId operationalForm.demoLoginDisplayName = response.demoLoginDisplayName operationalForm.twitchClientId = response.twitchClientId operationalForm.twitchRedirectUri = response.twitchRedirectUri operationalForm.twitchScope = response.twitchScope operationalForm.sessionIdleTimeoutHours = response.sessionIdleTimeoutHours operationalForm.maintenanceModeEnabled = response.maintenanceModeEnabled operationalForm.maintenanceTitle = response.maintenanceTitle operationalForm.maintenanceMessage = response.maintenanceMessage demoPasswordSet.value = response.demoLoginPasswordSet demoManagedByDatabase.value = response.demoLoginManagedByDatabase twitchClientSecretSet.value = response.twitchClientSecretSet twitchAuthConfigured.value = response.twitchAuthConfigured twitchAuthManagedByDatabase.value = response.twitchAuthManagedByDatabase demoPasswordInput.value = '' twitchClientSecretInput.value = '' rememberSavedOperationalSettings() } function buildOperationalSnapshot() { return JSON.stringify({ demoLoginEnabled: operationalForm.demoLoginEnabled, demoLoginIdentifier: operationalForm.demoLoginIdentifier, demoLoginTwitchUserId: operationalForm.demoLoginTwitchUserId, demoLoginDisplayName: operationalForm.demoLoginDisplayName, twitchClientId: operationalForm.twitchClientId, twitchRedirectUri: operationalForm.twitchRedirectUri, twitchScope: operationalForm.twitchScope, sessionIdleTimeoutHours: operationalForm.sessionIdleTimeoutHours, twitchClientSecretInput: twitchClientSecretInput.value, maintenanceModeEnabled: operationalForm.maintenanceModeEnabled, maintenanceTitle: operationalForm.maintenanceTitle, maintenanceMessage: operationalForm.maintenanceMessage, demoPasswordInput: demoPasswordInput.value, }) } function rememberSavedOperationalSettings() { savedOperationalSnapshot.value = buildOperationalSnapshot() } async function loadOperationalSettings(options: { silent?: boolean } = {}) { if (!options.silent) { operationalLoading.value = true operationalError.value = '' } try { applyOperationalSettings(await api.getAdminOperationalSettings()) } catch (error) { operationalError.value = error instanceof ApiRequestError ? error.message : 'Operational Settings konnten nicht geladen werden.' } finally { if (!options.silent) { operationalLoading.value = false } } } function validateOperationalSettings() { if (demoPasswordInput.value.trim() && demoPasswordInput.value.trim().length < 10) { operationalError.value = 'Das Demo-Passwort muss mindestens 10 Zeichen lang sein.' return false } if (operationalForm.demoLoginEnabled && !demoCredentialsComplete.value) { operationalError.value = 'Demo-Login braucht Login, Twitch-ID, Anzeigenamen und ein gesetztes Passwort.' return false } const hasAnyTwitchAuthInput = Boolean( operationalForm.twitchClientId.trim() || operationalForm.twitchRedirectUri.trim() || operationalForm.twitchScope.trim() || twitchClientSecretInput.value.trim() || twitchClientSecretSet.value, ) if (hasAnyTwitchAuthInput && !operationalForm.twitchClientId.trim()) { operationalError.value = 'Twitch OAuth braucht eine Client-ID.' return false } if (operationalForm.twitchClientId.trim() && !twitchAuthComplete.value) { operationalError.value = 'Twitch OAuth braucht beim ersten Speichern ein Client Secret.' return false } if (!Number.isInteger(operationalForm.sessionIdleTimeoutHours) || operationalForm.sessionIdleTimeoutHours < 3) { operationalError.value = 'Session-Timeout muss mindestens 3 Stunden betragen.' return false } return true } async function saveOperationalSettings() { operationalError.value = '' operationalSuccess.value = '' if (!validateOperationalSettings()) { return } operationalSaving.value = true try { const result = await api.updateAdminOperationalSettings({ ...operationalForm, demoLoginEmail: operationalForm.demoLoginIdentifier, demoLoginPassword: demoPasswordInput.value.trim() || undefined, twitchClientSecret: twitchClientSecretInput.value.trim() || undefined, }) demoPasswordSet.value = result.demoLoginPasswordSet demoManagedByDatabase.value = true twitchClientSecretSet.value = result.twitchClientSecretSet demoPasswordInput.value = '' twitchClientSecretInput.value = '' await loadOperationalSettings({ silent: true }) clearSiteStatusCache() operationalSuccess.value = 'Demo-Zugang, Session-Timeout und Wartungsmodus wurden gespeichert.' } catch (error) { operationalError.value = error instanceof ApiRequestError ? error.message : 'Operational Settings konnten nicht gespeichert werden.' } finally { operationalSaving.value = false } } watch( () => [ operationalForm.demoLoginEnabled, operationalForm.demoLoginIdentifier, operationalForm.demoLoginTwitchUserId, operationalForm.demoLoginDisplayName, operationalForm.twitchClientId, operationalForm.twitchRedirectUri, operationalForm.twitchScope, operationalForm.sessionIdleTimeoutHours, twitchClientSecretInput.value, operationalForm.maintenanceModeEnabled, operationalForm.maintenanceTitle, operationalForm.maintenanceMessage, demoPasswordInput.value, ], () => { if (operationalLoading.value || operationalSaving.value) return operationalError.value = '' operationalSuccess.value = '' }, ) onMounted(loadOperationalSettings) return { operationalLoading, operationalSaving, operationalError, operationalSuccess, operationalForm, demoPasswordSet, demoManagedByDatabase, demoPasswordInput, twitchClientSecretSet, twitchAuthConfigured, twitchAuthManagedByDatabase, twitchClientSecretInput, demoPasswordHint, twitchSecretHint, demoCredentialsComplete, twitchAuthComplete, operationalSummary, hasUnsavedOperationalChanges, loadOperationalSettings, saveOperationalSettings, } }