Merge pull request 'dev' (#53) from dev into main
All checks were successful
Code Analysis and Production Deploy / analyze (push) Has been skipped
Code Analysis and Production Deploy / deploy-production (push) Successful in 2m32s
Code Analysis and Production Deploy / deploy-test (push) Has been skipped

Reviewed-on: #53
This commit was merged in pull request #53.
This commit is contained in:
2026-09-09 13:51:00 +02:00
14 changed files with 321 additions and 184 deletions

View File

@@ -21,9 +21,20 @@ import de.harheimertc.ui.navigation.NavigationViewModel
import dagger.hilt.android.AndroidEntryPoint
import android.util.Log
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.lifecycleScope
import de.harheimertc.repositories.AuthRepository
import de.harheimertc.repositories.PushTokenRepository
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@Inject
lateinit var authRepository: AuthRepository
@Inject
lateinit var pushTokenRepository: PushTokenRepository
private val notificationRoute = mutableStateOf<String?>(null)
private val notificationPermissionLauncher = registerForActivityResult(
@@ -50,6 +61,23 @@ class MainActivity : ComponentActivity() {
notificationRoute.value = extractNotificationRoute(intent)
}
override fun onResume() {
super.onResume()
syncPushTokenWhenSignedIn()
}
/**
* Server-side tokens are persistent, but re-registering the current FCM
* token is cheap and idempotent. It restores a token if server data was
* ever recovered from a backup or manually repaired.
*/
private fun syncPushTokenWhenSignedIn() {
if (authRepository.getToken().isNullOrBlank() && authRepository.getRefreshToken().isNullOrBlank()) return
lifecycleScope.launch {
pushTokenRepository.registerCurrentDevice()
}
}
private fun extractNotificationRoute(intent: Intent?): String? =
intent?.getStringExtra(EXTRA_NOTIFICATION_ROUTE)?.takeIf { it.isNotBlank() }

View File

@@ -27,7 +27,10 @@ class PushTokenRepository @Inject constructor(
appVersion = "${BuildConfig.VERSION_NAME}+${BuildConfig.VERSION_CODE}",
),
)
if (!response.isSuccessful) error("Push-Token konnte nicht registriert werden.")
if (!response.isSuccessful) {
val detail = response.errorBody()?.string().orEmpty().replace(Regex("\\s+"), " ").take(180)
error("Push-Token konnte nicht registriert werden (HTTP ${response.code()})${if (detail.isBlank()) "" else ": $detail"}")
}
}
}.onFailure { error ->
Log.w("PushTokenRepository", "Push-Token Registrierung fehlgeschlagen", error)

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.harheimertc.repositories.LoginRepository
import de.harheimertc.repositories.PasskeyRepository
import de.harheimertc.repositories.PushTokenRepository
import de.harheimertc.ui.components.isValidEmail
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -29,6 +30,7 @@ data class LoginUiState(
class LoginViewModel @Inject constructor(
private val repository: LoginRepository,
private val passkeyRepository: PasskeyRepository,
private val pushTokenRepository: PushTokenRepository,
) : ViewModel() {
private val _state = MutableStateFlow(LoginUiState())
val state: StateFlow<LoginUiState> = _state
@@ -70,6 +72,7 @@ class LoginViewModel @Inject constructor(
_state.value = current.copy(loading = true, error = null, message = null)
repository.login(current.email, current.password)
.onSuccess { response ->
viewModelScope.launch { pushTokenRepository.registerCurrentDevice() }
_state.value = current.copy(
password = "",
loading = false,
@@ -92,6 +95,7 @@ class LoginViewModel @Inject constructor(
_state.value = current.copy(loading = true, error = null, message = null)
passkeyRepository.login(context, current.email)
.onSuccess { response ->
viewModelScope.launch { pushTokenRepository.registerCurrentDevice() }
_state.value = current.copy(
password = "",
loading = false,

View File

@@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import de.harheimertc.notifications.HarheimerNotifications
import de.harheimertc.BuildConfig
import de.harheimertc.repositories.Mannschaft
import de.harheimertc.repositories.NotificationPreferences
import de.harheimertc.ui.components.LoadingState
@@ -103,6 +104,23 @@ fun NotificationSettingsScreen(
}
}
item {
NotificationCard("Geräte-Registrierung") {
Text("Der Push-Token wird dem aktuell angemeldeten Konto auf diesem Server zugeordnet:", color = Accent700)
Text(BuildConfig.API_BASE_URL, color = Accent900, style = MaterialTheme.typography.bodySmall)
Button(
onClick = viewModel::registerCurrentDevice,
enabled = !state.deviceRegistrationInProgress,
modifier = Modifier.fillMaxWidth(),
) {
Text(if (state.deviceRegistrationInProgress) "Gerät wird registriert..." else "Android-Gerät jetzt registrieren")
}
state.deviceRegistrationMessage?.let { message ->
Text(message, color = if (state.deviceRegistrationError) MaterialTheme.colorScheme.error else Color(0xFF166534))
}
}
}
if (state.loading) {
item { LoadingState("Benachrichtigungseinstellungen werden geladen...") }
} else {

View File

@@ -8,6 +8,7 @@ import de.harheimertc.repositories.LoginRepository
import de.harheimertc.repositories.MannschaftenRepository
import de.harheimertc.repositories.NotificationPreferences
import de.harheimertc.repositories.NotificationPreferencesRepository
import de.harheimertc.repositories.PushTokenRepository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@@ -22,6 +23,9 @@ data class NotificationSettingsUiState(
val seasons: List<String> = emptyList(),
val error: String? = null,
val saveError: String? = null,
val deviceRegistrationMessage: String? = null,
val deviceRegistrationError: Boolean = false,
val deviceRegistrationInProgress: Boolean = false,
)
@HiltViewModel
@@ -29,6 +33,7 @@ class NotificationSettingsViewModel @Inject constructor(
private val preferencesRepository: NotificationPreferencesRepository,
private val mannschaftenRepository: MannschaftenRepository,
private val loginRepository: LoginRepository,
private val pushTokenRepository: PushTokenRepository,
) : ViewModel() {
private val _state = MutableStateFlow(NotificationSettingsUiState())
val state: StateFlow<NotificationSettingsUiState> = _state
@@ -82,6 +87,30 @@ class NotificationSettingsViewModel @Inject constructor(
update(current.copy(selectedTeamSlugs = nextTeams))
}
fun registerCurrentDevice() {
viewModelScope.launch {
_state.value = _state.value.copy(
deviceRegistrationInProgress = true,
deviceRegistrationMessage = null,
deviceRegistrationError = false,
)
pushTokenRepository.registerCurrentDevice()
.onSuccess {
_state.value = _state.value.copy(
deviceRegistrationInProgress = false,
deviceRegistrationMessage = "Dieses Android-Gerät wurde beim Server registriert.",
)
}
.onFailure { error ->
_state.value = _state.value.copy(
deviceRegistrationInProgress = false,
deviceRegistrationError = true,
deviceRegistrationMessage = error.message ?: "Android-Gerät konnte nicht registriert werden.",
)
}
}
}
private suspend fun loadTeams(settings: NotificationPreferences, seasons: List<String>, currentUserName: String, syncRemote: Boolean = false) {
mannschaftenRepository.fetchMannschaften(settings.selectedTeamSeason)
.onSuccess { teams ->

View File

@@ -8,8 +8,8 @@ LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
PRODUCTION_API_BASE_URL=https://harheimertc.de/
# Android app versioning for Play Store uploads
ANDROID_VERSION_CODE=33
ANDROID_VERSION_NAME=0.9.28
ANDROID_VERSION_CODE=35
ANDROID_VERSION_NAME=0.9.30
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
RELEASE_MINIFY_ENABLED=false

View File

@@ -56,14 +56,17 @@ has_tracked_files_under() {
install_dependencies() {
if [ -f "package-lock.json" ]; then
echo " Running: npm ci"
if ! npm ci; then
echo " Running: npm ci --no-audit"
# The registry audit endpoint is independent of package installation and
# can be temporarily unavailable (e.g. DNS EAI_AGAIN). Do not block a
# production rollout on that external advisory lookup.
if ! npm ci --no-audit --fund=false; then
echo " WARNING: npm ci fehlgeschlagen (Lockfile ggf. nicht synchron). Fallback auf npm install..."
npm install
npm install --no-audit --fund=false
fi
else
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
npm install
npm install --no-audit --fund=false
fi
}

View File

@@ -69,14 +69,17 @@ has_tracked_files_under() {
install_dependencies() {
if [ -f "package-lock.json" ]; then
echo " Running: npm ci"
if ! npm ci; then
echo " Running: npm ci --no-audit"
# The registry audit endpoint is independent of package installation and
# can be temporarily unavailable (e.g. DNS EAI_AGAIN). Do not block a
# test rollout on that external advisory lookup.
if ! npm ci --no-audit --fund=false; then
echo " WARNING: npm ci fehlgeschlagen (Lockfile ggf. nicht synchron). Fallback auf npm install..."
npm install
npm install --no-audit --fund=false
fi
else
echo " WARNING: package-lock.json fehlt. Führe npm install aus..."
npm install
npm install --no-audit --fund=false
fi
}

View File

@@ -10,7 +10,7 @@ git pull origin main
# Dependencies installieren
echo "📦 Installing dependencies..."
npm install
npm install --no-audit --fund=false
# Website bauen (Static Generation)
echo "🔨 Building website..."

370
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "harheimertc-website",
"version": "1.8.8",
"version": "1.8.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "harheimertc-website",
"version": "1.8.8",
"version": "1.8.9",
"hasInstallScript": true,
"dependencies": {
"@pinia/nuxt": "^0.11.2",
@@ -877,9 +877,9 @@
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.3.tgz",
"integrity": "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz",
"integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==",
"cpu": [
"arm64"
],
@@ -895,13 +895,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.3.2"
"@img/sharp-libvips-darwin-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.3.tgz",
"integrity": "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz",
"integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==",
"cpu": [
"x64"
],
@@ -917,20 +917,20 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.3.2"
"@img/sharp-libvips-darwin-x64": "1.3.3"
}
},
"node_modules/@img/sharp-freebsd-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
"integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz",
"integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==",
"license": "Apache-2.0",
"optional": true,
"os": [
"freebsd"
],
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
"@img/sharp-wasm32": "0.35.4"
},
"engines": {
"node": ">=20.9.0"
@@ -940,9 +940,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.2.tgz",
"integrity": "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz",
"integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==",
"cpu": [
"arm64"
],
@@ -956,9 +956,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.2.tgz",
"integrity": "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz",
"integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==",
"cpu": [
"x64"
],
@@ -972,9 +972,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.2.tgz",
"integrity": "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz",
"integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==",
"cpu": [
"arm"
],
@@ -988,9 +988,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.2.tgz",
"integrity": "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz",
"integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==",
"cpu": [
"arm64"
],
@@ -1004,9 +1004,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.2.tgz",
"integrity": "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz",
"integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==",
"cpu": [
"ppc64"
],
@@ -1020,9 +1020,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.2.tgz",
"integrity": "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz",
"integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==",
"cpu": [
"riscv64"
],
@@ -1036,9 +1036,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.2.tgz",
"integrity": "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz",
"integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==",
"cpu": [
"s390x"
],
@@ -1052,9 +1052,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.2.tgz",
"integrity": "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz",
"integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==",
"cpu": [
"x64"
],
@@ -1068,9 +1068,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.2.tgz",
"integrity": "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz",
"integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==",
"cpu": [
"arm64"
],
@@ -1084,9 +1084,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.2.tgz",
"integrity": "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz",
"integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==",
"cpu": [
"x64"
],
@@ -1100,9 +1100,9 @@
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.3.tgz",
"integrity": "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz",
"integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==",
"cpu": [
"arm"
],
@@ -1118,13 +1118,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.3.2"
"@img/sharp-libvips-linux-arm": "1.3.3"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.3.tgz",
"integrity": "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz",
"integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==",
"cpu": [
"arm64"
],
@@ -1140,13 +1140,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.3.2"
"@img/sharp-libvips-linux-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.3.tgz",
"integrity": "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz",
"integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==",
"cpu": [
"ppc64"
],
@@ -1162,13 +1162,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.3.2"
"@img/sharp-libvips-linux-ppc64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.3.tgz",
"integrity": "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz",
"integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==",
"cpu": [
"riscv64"
],
@@ -1184,13 +1184,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.3.2"
"@img/sharp-libvips-linux-riscv64": "1.3.3"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.3.tgz",
"integrity": "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz",
"integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==",
"cpu": [
"s390x"
],
@@ -1206,13 +1206,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.3.2"
"@img/sharp-libvips-linux-s390x": "1.3.3"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.3.tgz",
"integrity": "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz",
"integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==",
"cpu": [
"x64"
],
@@ -1228,13 +1228,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.3.2"
"@img/sharp-libvips-linux-x64": "1.3.3"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.3.tgz",
"integrity": "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz",
"integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==",
"cpu": [
"arm64"
],
@@ -1250,13 +1250,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2"
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.3.tgz",
"integrity": "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz",
"integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==",
"cpu": [
"x64"
],
@@ -1272,17 +1272,17 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.3.2"
"@img/sharp-libvips-linuxmusl-x64": "1.3.3"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
"integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz",
"integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==",
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.11.1"
"@emnapi/runtime": "^1.11.3"
},
"engines": {
"node": ">=20.9.0"
@@ -1292,16 +1292,16 @@
}
},
"node_modules/@img/sharp-webcontainers-wasm32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.3.tgz",
"integrity": "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz",
"integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==",
"cpu": [
"wasm32"
],
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@img/sharp-wasm32": "0.35.3"
"@img/sharp-wasm32": "0.35.4"
},
"engines": {
"node": ">=20.9.0"
@@ -1311,9 +1311,9 @@
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.3.tgz",
"integrity": "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz",
"integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==",
"cpu": [
"arm64"
],
@@ -1330,9 +1330,9 @@
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.3.tgz",
"integrity": "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz",
"integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==",
"cpu": [
"ia32"
],
@@ -1349,9 +1349,9 @@
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.3.tgz",
"integrity": "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz",
"integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==",
"cpu": [
"x64"
],
@@ -3129,6 +3129,8 @@
},
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
"dev": true,
"license": "MIT"
},
@@ -3144,6 +3146,8 @@
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3153,6 +3157,8 @@
},
"node_modules/@types/deep-eql": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
"dev": true,
"license": "MIT"
},
@@ -3344,14 +3350,16 @@
}
},
"node_modules/@vitest/expect": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
"integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.1.10",
"@vitest/utils": "4.1.10",
"@vitest/spy": "4.1.11",
"@vitest/utils": "4.1.11",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -3360,11 +3368,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
"integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.1.10",
"@vitest/spy": "4.1.11",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -3386,6 +3396,8 @@
},
"node_modules/@vitest/mocker/node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3393,7 +3405,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
"integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3404,11 +3418,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
"integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.1.10",
"@vitest/utils": "4.1.11",
"pathe": "^2.0.3"
},
"funding": {
@@ -3416,12 +3432,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
"integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.10",
"@vitest/utils": "4.1.10",
"@vitest/pretty-format": "4.1.11",
"@vitest/utils": "4.1.11",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -3430,7 +3448,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
"integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -3438,11 +3458,13 @@
}
},
"node_modules/@vitest/utils": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
"integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.10",
"@vitest/pretty-format": "4.1.11",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -4033,6 +4055,8 @@
},
"node_modules/assertion-error": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4535,6 +4559,8 @@
},
"node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -4912,16 +4938,16 @@
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz",
"integrity": "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
"css-what": "^7.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"nth-check": "^2.1.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
@@ -4941,9 +4967,9 @@
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-7.0.0.tgz",
"integrity": "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
@@ -7592,7 +7618,9 @@
"license": "MIT"
},
"node_modules/multer": {
"version": "2.2.0",
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.3.0.tgz",
"integrity": "sha512-cjNbm3sttszgZeGfJR124D+jFEfkXCVAsoPBmFn9X7UxmDSFHWqE2CoEj0vrmSpuAFnqWR1Szcm9QTsiHr60Xw==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
@@ -7830,7 +7858,9 @@
}
},
"node_modules/nodemailer": {
"version": "9.0.3",
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.1.1.tgz",
"integrity": "sha512-izw9mVKFix6YSnC9eLgV6g1opl9DUlRio9ZNcq+Wu9Ujn2UwF+8Nl0B8nz22kEC+CTZCvinkxwJ0DeFbb6NwcQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
@@ -10236,9 +10266,9 @@
"license": "ISC"
},
"node_modules/sharp": {
"version": "0.35.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
"integrity": "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==",
"version": "0.35.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz",
"integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==",
"license": "Apache-2.0",
"dependencies": {
"@img/colour": "^1.1.0",
@@ -10252,31 +10282,31 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.35.3",
"@img/sharp-darwin-x64": "0.35.3",
"@img/sharp-freebsd-wasm32": "0.35.3",
"@img/sharp-libvips-darwin-arm64": "1.3.2",
"@img/sharp-libvips-darwin-x64": "1.3.2",
"@img/sharp-libvips-linux-arm": "1.3.2",
"@img/sharp-libvips-linux-arm64": "1.3.2",
"@img/sharp-libvips-linux-ppc64": "1.3.2",
"@img/sharp-libvips-linux-riscv64": "1.3.2",
"@img/sharp-libvips-linux-s390x": "1.3.2",
"@img/sharp-libvips-linux-x64": "1.3.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.2",
"@img/sharp-libvips-linuxmusl-x64": "1.3.2",
"@img/sharp-linux-arm": "0.35.3",
"@img/sharp-linux-arm64": "0.35.3",
"@img/sharp-linux-ppc64": "0.35.3",
"@img/sharp-linux-riscv64": "0.35.3",
"@img/sharp-linux-s390x": "0.35.3",
"@img/sharp-linux-x64": "0.35.3",
"@img/sharp-linuxmusl-arm64": "0.35.3",
"@img/sharp-linuxmusl-x64": "0.35.3",
"@img/sharp-webcontainers-wasm32": "0.35.3",
"@img/sharp-win32-arm64": "0.35.3",
"@img/sharp-win32-ia32": "0.35.3",
"@img/sharp-win32-x64": "0.35.3"
"@img/sharp-darwin-arm64": "0.35.4",
"@img/sharp-darwin-x64": "0.35.4",
"@img/sharp-freebsd-wasm32": "0.35.4",
"@img/sharp-libvips-darwin-arm64": "1.3.3",
"@img/sharp-libvips-darwin-x64": "1.3.3",
"@img/sharp-libvips-linux-arm": "1.3.3",
"@img/sharp-libvips-linux-arm64": "1.3.3",
"@img/sharp-libvips-linux-ppc64": "1.3.3",
"@img/sharp-libvips-linux-riscv64": "1.3.3",
"@img/sharp-libvips-linux-s390x": "1.3.3",
"@img/sharp-libvips-linux-x64": "1.3.3",
"@img/sharp-libvips-linuxmusl-arm64": "1.3.3",
"@img/sharp-libvips-linuxmusl-x64": "1.3.3",
"@img/sharp-linux-arm": "0.35.4",
"@img/sharp-linux-arm64": "0.35.4",
"@img/sharp-linux-ppc64": "0.35.4",
"@img/sharp-linux-riscv64": "0.35.4",
"@img/sharp-linux-s390x": "0.35.4",
"@img/sharp-linux-x64": "0.35.4",
"@img/sharp-linuxmusl-arm64": "0.35.4",
"@img/sharp-linuxmusl-x64": "0.35.4",
"@img/sharp-webcontainers-wasm32": "0.35.4",
"@img/sharp-win32-arm64": "0.35.4",
"@img/sharp-win32-ia32": "0.35.4",
"@img/sharp-win32-x64": "0.35.4"
},
"peerDependenciesMeta": {
"@types/node": {
@@ -10777,18 +10807,18 @@
}
},
"node_modules/svgo": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.2.tgz",
"integrity": "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.1.0.tgz",
"integrity": "sha512-bkxnTg1kSU0guhIBmibA6UUhrQmPVA1XsQLN+ylCd+UWzbnLkySOcXpyk1mrl05f+pcaCx2eHb+sp6BgMZWX+Q==",
"license": "MIT",
"dependencies": {
"commander": "^11.1.0",
"css-select": "^5.1.0",
"css-select": "^6.0.0",
"css-tree": "^3.0.1",
"css-what": "^6.1.0",
"css-what": "^7.0.0",
"csso": "^5.0.5",
"picocolors": "^1.1.1",
"sax": "^1.5.0"
"sax": "1.6.1"
},
"bin": {
"svgo": "bin/svgo.js"
@@ -11141,7 +11171,9 @@
}
},
"node_modules/tinyrainbow": {
"version": "3.1.0",
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
"integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12030,17 +12062,19 @@
}
},
"node_modules/vitest": {
"version": "4.1.10",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
"integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.1.10",
"@vitest/mocker": "4.1.10",
"@vitest/pretty-format": "4.1.10",
"@vitest/runner": "4.1.10",
"@vitest/snapshot": "4.1.10",
"@vitest/spy": "4.1.10",
"@vitest/utils": "4.1.10",
"@vitest/expect": "4.1.11",
"@vitest/mocker": "4.1.11",
"@vitest/pretty-format": "4.1.11",
"@vitest/runner": "4.1.11",
"@vitest/snapshot": "4.1.11",
"@vitest/spy": "4.1.11",
"@vitest/utils": "4.1.11",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -12068,12 +12102,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.10",
"@vitest/browser-preview": "4.1.10",
"@vitest/browser-webdriverio": "4.1.10",
"@vitest/coverage-istanbul": "4.1.10",
"@vitest/coverage-v8": "4.1.10",
"@vitest/ui": "4.1.10",
"@vitest/browser-playwright": "4.1.11",
"@vitest/browser-preview": "4.1.11",
"@vitest/browser-webdriverio": "4.1.11",
"@vitest/coverage-istanbul": "4.1.11",
"@vitest/coverage-v8": "4.1.11",
"@vitest/ui": "4.1.11",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"

View File

@@ -47,6 +47,6 @@ const formattedLastImport = computed(() => status.value.lastSuccessfulImportAt ?
async function load() { status.value = await $fetch('/api/cms/mytischtennis') }
async function save() { busy.value = true; message.value = ''; error.value = false; try { status.value = await $fetch('/api/cms/mytischtennis', { method: 'PUT', body: form }); form.password = ''; message.value = 'Zugang verschlüsselt gespeichert.' } catch { error.value = true; message.value = 'Zugang konnte nicht gespeichert werden.' } finally { busy.value = false } }
async function runImport() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/mytischtennis/import', { method: 'POST' }); message.value = `${result.rowCount} Werte wurden aktualisiert.`; await load() } catch (caught) { error.value = true; message.value = caught?.data?.message || 'Abruf fehlgeschlagen. Details stehen im Serverlog.'; await load().catch(() => {}) } finally { busy.value = false } }
async function sendTestPush() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/notifications/test', { method: 'POST' }); message.value = result.sent > 0 ? 'Push-Test wurde an deine registrierten Android-Geräte gesendet.' : 'Kein registriertes Android-Gerät gefunden. Bitte App einmal öffnen und Benachrichtigungen erlauben.' } catch (caught) { error.value = true; message.value = caught?.data?.statusMessage || caught?.data?.message || 'Push-Test konnte nicht gesendet werden.' } finally { busy.value = false } }
async function sendTestPush() { busy.value = true; message.value = ''; error.value = false; try { const result = await $fetch('/api/cms/notifications/test', { method: 'POST' }); if (result.registeredTokenCount === 0) { message.value = 'Kein Android-Token für dieses Konto gespeichert.'; error.value = true } else if (result.skipped) { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, aber der FCM-Push-Dienst ist auf dem Server nicht konfiguriert.`; error.value = true } else if (result.deliveryError) { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, aber: ${result.deliveryError}`; error.value = true } else if (result.sent > 0) { message.value = `Push-Test wurde an ${result.sent} registrierte(s) Android-Gerät(e) gesendet.` } else { message.value = `${result.registeredTokenCount} Android-Gerät(e) registriert, Zustellung fehlgeschlagen. Bitte Serverlog prüfen.`; error.value = true } } catch (caught) { error.value = true; message.value = caught?.data?.statusMessage || caught?.data?.message || 'Push-Test konnte nicht gesendet werden.' } finally { busy.value = false } }
onMounted(load)
</script>

View File

@@ -1,5 +1,5 @@
import { getUserFromToken, hasRole } from '../../../utils/auth.js'
import { sendTestPushToUser } from '../../../utils/push-notifications.js'
import { androidPushTokenCountForUser, sendTestPushToUser } from '../../../utils/push-notifications.js'
export default defineEventHandler(async (event) => {
const token = getCookie(event, 'auth_token') || getHeader(event, 'authorization')?.replace(/^Bearer\s+/i, '')
@@ -7,6 +7,16 @@ export default defineEventHandler(async (event) => {
if (!user) throw createError({ statusCode: 401, statusMessage: 'Nicht authentifiziert' })
if (!hasRole(user, 'admin')) throw createError({ statusCode: 403, statusMessage: 'Nur Administratoren dürfen einen Push-Test auslösen.' })
const result = await sendTestPushToUser(user.id)
return { success: true, ...result }
const registeredTokenCount = await androidPushTokenCountForUser(user.id)
try {
const result = await sendTestPushToUser(user.id)
return { success: true, registeredTokenCount, ...result }
} catch (error) {
console.error('FCM Test-Push konnte nicht vorbereitet werden:', error)
return {
success: false,
registeredTokenCount,
deliveryError: 'Die FCM-Serververbindung konnte nicht aufgebaut werden. Details stehen im Serverlog.'
}
}
})

View File

@@ -102,6 +102,11 @@ export function upsertPushToken(user, { token, platform = 'android', appVersion
return user
}
export async function androidPushTokenCountForUser(userId) {
const user = (await readUsers()).find(entry => entry?.id === userId)
return pushTokensForUser(user).length
}
async function sendFcmMessage({ serviceAccount, accessToken, token, data = {} }) {
const projectId = projectIdFromServiceAccount(serviceAccount)
if (!projectId) throw new Error('FCM project_id fehlt.')