Bugfixing
This commit is contained in:
BIN
android-app/app/production/release/app-production-release.aab
Normal file
BIN
android-app/app/production/release/app-production-release.aab
Normal file
Binary file not shown.
@@ -2,7 +2,9 @@ package de.harheimertc.ui.navigation
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
@@ -10,6 +12,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -327,46 +330,141 @@ fun NavGraph(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsStartseite.route) {
|
composable(Destinations.CmsStartseite.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsStartseiteScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsStartseiteScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsInhalte.route) {
|
composable(Destinations.CmsInhalte.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsInhalteScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsInhalteScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsVereinsmeisterschaften.route) {
|
composable(Destinations.CmsVereinsmeisterschaften.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsVereinsmeisterschaftenScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsVereinsmeisterschaftenScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsNews.route) {
|
composable(Destinations.CmsNews.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsNewsScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsNewsScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsSportbetrieb.route) {
|
composable(Destinations.CmsSportbetrieb.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsSportbetriebScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsSportbetriebScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsMitgliederverwaltung.route) {
|
composable(Destinations.CmsMitgliederverwaltung.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsMitgliederverwaltungScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsMitgliederverwaltungScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsNewsletter.route) {
|
composable(Destinations.CmsNewsletter.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsNewsletterScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessNewsletter,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsNewsletterScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsContactRequests.route) {
|
composable(Destinations.CmsContactRequests.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsContactRequestsScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessContactRequests,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsContactRequestsScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsEinstellungen.route) {
|
composable(Destinations.CmsEinstellungen.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsEinstellungenScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsEinstellungenScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsBenutzer.route) {
|
composable(Destinations.CmsBenutzer.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsBenutzerScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsBenutzerScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.CmsPasswordResetDiagnostics.route) {
|
composable(Destinations.CmsPasswordResetDiagnostics.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsPasswordResetDiagnosticsScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessFullCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsPasswordResetDiagnosticsScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
composable(Destinations.Cms.route) {
|
composable(Destinations.Cms.route) {
|
||||||
de.harheimertc.ui.screens.cms.CmsDashboardScreen(navController, !persistentNavigation)
|
RequireCmsAccess(
|
||||||
|
allowed = navigationState.canAccessCms,
|
||||||
|
navController = navController,
|
||||||
|
) {
|
||||||
|
de.harheimertc.ui.screens.cms.CmsDashboardScreen(navController, !persistentNavigation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RequireCmsAccess(
|
||||||
|
allowed: Boolean,
|
||||||
|
navController: NavHostController,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
if (allowed) {
|
||||||
|
content()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(24.dp),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Kein Zugriff auf diesen Bereich.",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = Color(0xFF7F1D1D),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "Bitte mit einem berechtigten Konto anmelden.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color(0xFF7C2D12),
|
||||||
|
modifier = Modifier.padding(top = 8.dp, bottom = 16.dp),
|
||||||
|
)
|
||||||
|
Button(onClick = { navController.navigateTopLevel(Destinations.Login.route) }) {
|
||||||
|
Text("Zum Login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun NavHostController.navigateTopLevel(route: String) {
|
private fun NavHostController.navigateTopLevel(route: String) {
|
||||||
val isTeamDetail = route.startsWith("mannschaften/") &&
|
val isTeamDetail = route.startsWith("mannschaften/") &&
|
||||||
route != Destinations.Spielsysteme.route
|
route != Destinations.Spielsysteme.route
|
||||||
|
|||||||
@@ -132,15 +132,22 @@ fun CmsNewsScreen(navController: NavController, showBackNavigation: Boolean, vie
|
|||||||
|
|
||||||
items(state.news) { news ->
|
items(state.news) { news ->
|
||||||
val selected = news.id?.let { selection.contains(it) } ?: false
|
val selected = news.id?.let { selection.contains(it) } ?: false
|
||||||
NewsListItem(news = news, selected = selected, onSelect = { id, sel ->
|
NewsListItem(
|
||||||
id?.let {
|
news = news,
|
||||||
selection = if (sel) selection + it else selection - it
|
canWrite = canWrite,
|
||||||
}
|
selected = selected,
|
||||||
}, onEdit = { openEdit(news) }, onDelete = { news.id?.let { id -> deletingIds = listOf(id) } })
|
onSelect = { id, sel ->
|
||||||
|
id?.let {
|
||||||
|
selection = if (sel) selection + it else selection - it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEdit = { openEdit(news) },
|
||||||
|
onDelete = { news.id?.let { id -> deletingIds = listOf(id) } },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bulk action bar
|
// bulk action bar
|
||||||
if (selection.isNotEmpty()) {
|
if (canWrite && selection.isNotEmpty()) {
|
||||||
item {
|
item {
|
||||||
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Button(onClick = { viewModel.bulkSetPublic(selection.toList(), true) }) { Text("Als öffentlich markieren") }
|
Button(onClick = { viewModel.bulkSetPublic(selection.toList(), true) }) { Text("Als öffentlich markieren") }
|
||||||
@@ -255,6 +262,7 @@ fun CmsNewsScreen(navController: NavController, showBackNavigation: Boolean, vie
|
|||||||
@Composable
|
@Composable
|
||||||
private fun NewsListItem(
|
private fun NewsListItem(
|
||||||
news: NewsDto,
|
news: NewsDto,
|
||||||
|
canWrite: Boolean,
|
||||||
selected: Boolean = false,
|
selected: Boolean = false,
|
||||||
onSelect: (String?, Boolean) -> Unit = { _, _ -> },
|
onSelect: (String?, Boolean) -> Unit = { _, _ -> },
|
||||||
onEdit: (NewsDto) -> Unit,
|
onEdit: (NewsDto) -> Unit,
|
||||||
@@ -264,7 +272,11 @@ private fun NewsListItem(
|
|||||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
|
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) {
|
||||||
Checkbox(checked = selected, onCheckedChange = { onSelect(news.id, it) })
|
Checkbox(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = { onSelect(news.id, it) },
|
||||||
|
enabled = canWrite,
|
||||||
|
)
|
||||||
Text(news.title.ifBlank { "(Ohne Titel)" }, modifier = Modifier.padding(start = 8.dp))
|
Text(news.title.ifBlank { "(Ohne Titel)" }, modifier = Modifier.padding(start = 8.dp))
|
||||||
if (news.isPublic) {
|
if (news.isPublic) {
|
||||||
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, modifier = Modifier.padding(start = 8.dp)) {
|
Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically, modifier = Modifier.padding(start = 8.dp)) {
|
||||||
@@ -297,9 +309,11 @@ private fun NewsListItem(
|
|||||||
Text("Aktualisiert: ${news.updated}")
|
Text("Aktualisiert: ${news.updated}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Row {
|
if (canWrite) {
|
||||||
TextButton(onClick = { onEdit(news) }) { Text("Bearbeiten") }
|
Row {
|
||||||
TextButton(onClick = { news.id?.let { onDelete(it) } }) { Text("Löschen") }
|
TextButton(onClick = { onEdit(news) }) { Text("Bearbeiten") }
|
||||||
|
TextButton(onClick = { news.id?.let { onDelete(it) } }) { Text("Löschen") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -872,7 +872,9 @@ fun CmsNewsletterScreen(
|
|||||||
if (!state.loading && state.newsletters.isEmpty()) item { EmptyCard("Keine Newsletter gefunden.") }
|
if (!state.loading && state.newsletters.isEmpty()) item { EmptyCard("Keine Newsletter gefunden.") }
|
||||||
items(state.newsletters.size) { index ->
|
items(state.newsletters.size) { index ->
|
||||||
val item = state.newsletters[index]
|
val item = state.newsletters[index]
|
||||||
NewsletterCard(item,
|
NewsletterCard(
|
||||||
|
item,
|
||||||
|
canWrite = canWrite,
|
||||||
onEdit = { nl ->
|
onEdit = { nl ->
|
||||||
editingNewsletter = nl
|
editingNewsletter = nl
|
||||||
nlTitle = nl.title
|
nlTitle = nl.title
|
||||||
@@ -890,7 +892,9 @@ fun CmsNewsletterScreen(
|
|||||||
if (!state.loading && state.newsletterGroups.isEmpty()) item { EmptyCard("Keine Gruppen gefunden.") }
|
if (!state.loading && state.newsletterGroups.isEmpty()) item { EmptyCard("Keine Gruppen gefunden.") }
|
||||||
items(state.newsletterGroups.size) { index ->
|
items(state.newsletterGroups.size) { index ->
|
||||||
val group = state.newsletterGroups[index]
|
val group = state.newsletterGroups[index]
|
||||||
NewsletterGroupCard(group,
|
NewsletterGroupCard(
|
||||||
|
group,
|
||||||
|
canWrite = canWrite,
|
||||||
onEdit = { g ->
|
onEdit = { g ->
|
||||||
editingGroup = g
|
editingGroup = g
|
||||||
grpName = g.name
|
grpName = g.name
|
||||||
@@ -1564,29 +1568,44 @@ private fun ContactRequestCard(request: ContactRequestDto, viewModel: CmsViewMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NewsletterCard(newsletter: NewsletterDto, onEdit: (NewsletterDto) -> Unit = {}, onDelete: (String) -> Unit = {}, onSend: (String) -> Unit = {}) {
|
private fun NewsletterCard(
|
||||||
|
newsletter: NewsletterDto,
|
||||||
|
canWrite: Boolean,
|
||||||
|
onEdit: (NewsletterDto) -> Unit = {},
|
||||||
|
onDelete: (String) -> Unit = {},
|
||||||
|
onSend: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
DataCard(newsletter.subject.ifBlank { newsletter.title.ifBlank { newsletter.id } }) {
|
DataCard(newsletter.subject.ifBlank { newsletter.title.ifBlank { newsletter.id } }) {
|
||||||
InfoRow("Status", newsletter.status ?: if (newsletter.sentAt != null) "versendet" else "Entwurf")
|
InfoRow("Status", newsletter.status ?: if (newsletter.sentAt != null) "versendet" else "Entwurf")
|
||||||
InfoRow("Erstellt", newsletter.createdAt ?: "-")
|
InfoRow("Erstellt", newsletter.createdAt ?: "-")
|
||||||
InfoRow("Versendet", newsletter.sentAt ?: "-")
|
InfoRow("Versendet", newsletter.sentAt ?: "-")
|
||||||
Row {
|
if (canWrite) {
|
||||||
TextButton(onClick = { onEdit(newsletter) }) { Text("Bearbeiten") }
|
Row {
|
||||||
TextButton(onClick = { newsletter.id.takeIf { it.isNotBlank() }?.let { onDelete(it) } }) { Text("Löschen") }
|
TextButton(onClick = { onEdit(newsletter) }) { Text("Bearbeiten") }
|
||||||
if (newsletter.status != "sent") {
|
TextButton(onClick = { newsletter.id.takeIf { it.isNotBlank() }?.let { onDelete(it) } }) { Text("Löschen") }
|
||||||
TextButton(onClick = { newsletter.id.takeIf { it.isNotBlank() }?.let { onSend(it) } }) { Text("Versenden") }
|
if (newsletter.status != "sent") {
|
||||||
|
TextButton(onClick = { newsletter.id.takeIf { it.isNotBlank() }?.let { onSend(it) } }) { Text("Versenden") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NewsletterGroupCard(group: NewsletterGroupDto, onEdit: (NewsletterGroupDto) -> Unit = {}, onDelete: (String) -> Unit = {}) {
|
private fun NewsletterGroupCard(
|
||||||
|
group: NewsletterGroupDto,
|
||||||
|
canWrite: Boolean,
|
||||||
|
onEdit: (NewsletterGroupDto) -> Unit = {},
|
||||||
|
onDelete: (String) -> Unit = {},
|
||||||
|
) {
|
||||||
DataCard(group.name.ifBlank { group.id }) {
|
DataCard(group.name.ifBlank { group.id }) {
|
||||||
InfoRow("Beschreibung", group.description.ifBlank { "-" })
|
InfoRow("Beschreibung", group.description.ifBlank { "-" })
|
||||||
InfoRow("Abonnenten", group.subscribers.size.toString())
|
InfoRow("Abonnenten", group.subscribers.size.toString())
|
||||||
Row {
|
if (canWrite) {
|
||||||
TextButton(onClick = { onEdit(group) }) { Text("Bearbeiten") }
|
Row {
|
||||||
TextButton(onClick = { group.id.takeIf { it.isNotBlank() }?.let { onDelete(it) } }) { Text("Löschen") }
|
TextButton(onClick = { onEdit(group) }) { Text("Bearbeiten") }
|
||||||
|
TextButton(onClick = { group.id.takeIf { it.isNotBlank() }?.let { onDelete(it) } }) { Text("Löschen") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,20 +117,22 @@ fun HomeScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
item {
|
if (navigationState.canAccessFullCms) {
|
||||||
HomeCustomizationSection(
|
item {
|
||||||
sections = state.homepageSections,
|
HomeCustomizationSection(
|
||||||
spielplanSeasons = state.spielplanSeasons,
|
sections = state.homepageSections,
|
||||||
spielplanTeamsBySeason = state.spielplanTeamsBySeason,
|
spielplanSeasons = state.spielplanSeasons,
|
||||||
editEnabled = editHomeSections,
|
spielplanTeamsBySeason = state.spielplanTeamsBySeason,
|
||||||
onToggleEdit = { editHomeSections = !editHomeSections },
|
editEnabled = editHomeSections,
|
||||||
onMoveUp = viewModel::moveSectionUp,
|
onToggleEdit = { editHomeSections = !editHomeSections },
|
||||||
onMoveDown = viewModel::moveSectionDown,
|
onMoveUp = viewModel::moveSectionUp,
|
||||||
onEnabledChange = viewModel::setSectionEnabled,
|
onMoveDown = viewModel::moveSectionDown,
|
||||||
onAddSpielplanWidget = viewModel::addSpielplanTeamWidget,
|
onEnabledChange = viewModel::setSectionEnabled,
|
||||||
onUpdateSpielplanWidget = viewModel::updateSpielplanTeamWidget,
|
onAddSpielplanWidget = viewModel::addSpielplanTeamWidget,
|
||||||
onReset = viewModel::resetSections,
|
onUpdateSpielplanWidget = viewModel::updateSpielplanTeamWidget,
|
||||||
)
|
onReset = viewModel::resetSections,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (state.error) {
|
if (state.error) {
|
||||||
item {
|
item {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ LOCAL_API_BASE_URL=https://harheimertc.tsschulz.de/
|
|||||||
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
PRODUCTION_API_BASE_URL=https://harheimertc.de/
|
||||||
|
|
||||||
# Android app versioning for Play Store uploads
|
# Android app versioning for Play Store uploads
|
||||||
ANDROID_VERSION_CODE=26
|
ANDROID_VERSION_CODE=28
|
||||||
ANDROID_VERSION_NAME=0.9.21
|
ANDROID_VERSION_NAME=0.9.23
|
||||||
|
|
||||||
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
# Temporary hotfix: disable R8 minification for release to avoid Retrofit generic signature stripping.
|
||||||
RELEASE_MINIFY_ENABLED=false
|
RELEASE_MINIFY_ENABLED=false
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harheimertc-website",
|
"name": "harheimertc-website",
|
||||||
"version": "1.8.1",
|
"version": "1.8.2",
|
||||||
"description": "Moderne Webseite für den Harheimer Tischtennis Club",
|
"description": "Moderne Webseite für den Harheimer Tischtennis Club",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { requireUserWithAnyRole } from '../../utils/auth.js'
|
||||||
import { decryptObject } from '../../utils/encryption.js'
|
import { decryptObject } from '../../utils/encryption.js'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
// Nur Vorstand oder Admin darf Mitgliedschaftsantraege lesen
|
await requireUserWithAnyRole(event, 'admin', 'vorstand')
|
||||||
const token = getCookie(event, 'auth_token')
|
|
||||||
const currentUser = token ? await getUserFromToken(token) : null
|
|
||||||
if (!currentUser || !hasAnyRole(currentUser, 'admin', 'vorstand')) {
|
|
||||||
throw createError({ statusCode: 403, statusMessage: 'Zugriff verweigert' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const encryptionKey = config.encryptionKey || 'local_development_encryption_key_change_in_production'
|
const encryptionKey = config.encryptionKey || 'local_development_encryption_key_change_in_production'
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
import { requireUserWithAnyRole } from '../../utils/auth.js'
|
||||||
import { decryptObject } from '../../utils/encryption.js'
|
import { decryptObject } from '../../utils/encryption.js'
|
||||||
import { saveMember } from '../../utils/members.js'
|
import { saveMember } from '../../utils/members.js'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
try {
|
try {
|
||||||
|
await requireUserWithAnyRole(event, 'admin', 'vorstand')
|
||||||
|
|
||||||
const { id, status, notes } = await readBody(event)
|
const { id, status, notes } = await readBody(event)
|
||||||
|
|
||||||
if (!id || !status) {
|
if (!id || !status) {
|
||||||
|
|||||||
@@ -399,6 +399,42 @@ export async function getUserFromToken(token) {
|
|||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTokenFromEvent(event) {
|
||||||
|
return getCookie(event, 'auth_token') || getHeader(event, 'authorization')?.replace(/^Bearer\s+/i, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireAuthenticatedUser(event) {
|
||||||
|
const token = getTokenFromEvent(event)
|
||||||
|
if (!token) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Nicht authentifiziert'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await getUserFromToken(token)
|
||||||
|
if (!user) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 401,
|
||||||
|
statusMessage: 'Ungültige Sitzung'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requireUserWithAnyRole(event, ...roles) {
|
||||||
|
const user = await requireAuthenticatedUser(event)
|
||||||
|
if (!hasAnyRole(user, ...roles)) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 403,
|
||||||
|
statusMessage: 'Zugriff verweigert'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
// Create session
|
// Create session
|
||||||
export async function createSession(userId, token) {
|
export async function createSession(userId, token) {
|
||||||
const sessions = await readSessions()
|
const sessions = await readSessions()
|
||||||
|
|||||||
Reference in New Issue
Block a user