From 40bd5e074551331221fc2e7cac9faa26e1444897 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 15 May 2026 16:52:26 +0200 Subject: [PATCH] feat: improve user token handling and add club selection clearing button in settings --- .../diaryDateActivityController.js | 47 +++- backend/services/diaryDateActivityService.js | 220 ++++++------------ .../de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt | 14 ++ 3 files changed, 119 insertions(+), 162 deletions(-) diff --git a/backend/controllers/diaryDateActivityController.js b/backend/controllers/diaryDateActivityController.js index 250f7d51..409f8046 100644 --- a/backend/controllers/diaryDateActivityController.js +++ b/backend/controllers/diaryDateActivityController.js @@ -1,11 +1,16 @@ +import fs from 'fs'; import diaryDateActivityService from '../services/diaryDateActivityService.js'; import { emitActivityChanged } from '../services/socketService.js'; import DiaryDate from '../models/DiaryDates.js'; -import { devLog } from '../utils/logger.js'; +import { devLog, errorLog } from '../utils/logger.js'; export const createDiaryDateActivity = async (req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId } = req.params; const { diaryDateId, activity, predefinedActivityId, duration, durationText, orderId, isTimeblock, groupId } = req.body; const activityItem = await diaryDateActivityService.createActivity(userToken, clubId, { @@ -34,7 +39,11 @@ export const createDiaryDateActivity = async (req, res) => { export const updateDiaryDateActivity = async (req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId, id } = req.params; const { predefinedActivityId, customActivityName, duration, durationText, orderId, groupId } = req.body; // Add groupId const updatedActivity = await diaryDateActivityService.updateActivity(userToken, clubId, id, { @@ -62,7 +71,11 @@ export const updateDiaryDateActivity = async (req, res) => { export const deleteDiaryDateActivity = async (req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId, id } = req.params; // Hole diaryDateId vor dem Löschen @@ -88,7 +101,11 @@ export const deleteDiaryDateActivity = async (req, res) => { export const updateDiaryDateActivityOrder = async (req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId, id } = req.params; const { orderId } = req.body; const updatedActivity = await diaryDateActivityService.updateActivityOrder(userToken, clubId, id, orderId); @@ -110,19 +127,35 @@ export const updateDiaryDateActivityOrder = async (req, res) => { export const getDiaryDateActivities = async (req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId, diaryDateId } = req.params; const activities = await diaryDateActivityService.getActivities(userToken, clubId, diaryDateId); res.status(200).json(activities); } catch (error) { + // Fallback-Logging: schreibe Stacktrace in eine Datei, falls STDOUT/STDERR nicht sichtbar ist + try { + const msg = `${new Date().toISOString()} - getDiaryDateActivities error: ${error && error.stack ? error.stack : JSON.stringify(error)}\n`; + fs.appendFileSync('/tmp/diary-activity-error.log', msg); + } catch (e) { + // ignore + } devLog(error); + errorLog(error); res.status(500).json({ error: 'Error getting activities' }); } } export const addGroupActivity = async(req, res) => { try { - const { authcode: userToken } = req.headers; + let userToken = req.headers['authcode'] || req.headers['auth-code'] || null; + const authHeader = req.headers['authorization'] || req.headers['Authorization']; + if (!userToken && authHeader && typeof authHeader === 'string' && authHeader.startsWith('Bearer ')) { + userToken = authHeader.split(' ')[1]; + } const { clubId, diaryDateId, groupId, activity, predefinedActivityId, timeblockId, duration, durationText } = req.body; const activityItem = await diaryDateActivityService.addGroupActivity( userToken, diff --git a/backend/services/diaryDateActivityService.js b/backend/services/diaryDateActivityService.js index 8c6f6d75..5056b67b 100644 --- a/backend/services/diaryDateActivityService.js +++ b/backend/services/diaryDateActivityService.js @@ -177,178 +177,88 @@ class DiaryDateActivityService { async getActivities(userToken, clubId, diaryDateId) { await checkAccess(userToken, clubId); + // Avoid Sequelize eager-loading includes here to prevent EagerLoadingErrors + // if associations are not initialized in certain environments. Instead, + // load the core DiaryDateActivity rows and fetch related models separately. const activities = await DiaryDateActivity.findAll({ where: { diaryDateId }, order: [['orderId', 'ASC']], - include: [ - { - model: PredefinedActivity, - as: 'predefinedActivity', - include: [ - { - model: PredefinedActivityImage, - as: 'images' - } - ] - }, - { - model: Group, - as: 'planGroup' - }, - { - model: GroupActivity, - as: 'groupActivities', - separate: true, - order: [['orderId', 'ASC'], ['id', 'ASC']], - include: [ - { - model: Group, - as: 'groupsGroupActivity' - }, - { - model: PredefinedActivity, - as: 'groupPredefinedActivity', - include: [ - { - model: PredefinedActivityImage, - as: 'images' - } - ] - }, - ], - } - ] }); // Füge imageUrl zu jeder PredefinedActivity hinzu - const activitiesWithImages = await Promise.all(activities.map(async activity => { - // Konvertiere zu JSON und zurück, um alle Eigenschaften zu serialisieren + const activitiesWithImages = []; + for (const activity of activities) { const activityData = activity.toJSON(); - - if (activityData.predefinedActivity) { - // Hole alle Images aus der Datenbank - const allImages = await PredefinedActivityImage.findAll({ - where: { predefinedActivityId: activityData.predefinedActivity.id }, - order: [['createdAt', 'ASC']] - }); - - // Konvertiere Images zu JSON und parse drawingData falls vorhanden - const imagesWithParsedData = allImages.map(img => { - const imgData = img.toJSON(); - if (imgData.drawingData) { - try { - imgData.drawingData = JSON.parse(imgData.drawingData); - } catch (error) { - console.error(`Image ${imgData.id}: Error parsing drawingData:`, error); + + // Load predefinedActivity if present + if (activityData.predefinedActivityId) { + const predefined = await PredefinedActivity.findByPk(activityData.predefinedActivityId); + activityData.predefinedActivity = predefined ? predefined.toJSON() : null; + + if (activityData.predefinedActivity) { + const allImages = await PredefinedActivityImage.findAll({ + where: { predefinedActivityId: activityData.predefinedActivity.id }, + order: [['createdAt', 'ASC']] + }); + const imagesWithParsedData = allImages.map(img => { + const imgData = img.toJSON(); + if (imgData.drawingData) { + try { imgData.drawingData = JSON.parse(imgData.drawingData); } catch (err) { console.error(err); } } + return imgData; + }); + activityData.predefinedActivity.images = imagesWithParsedData; + const firstImage = allImages.length > 0 ? allImages[0] : null; + if (activityData.predefinedActivity.drawingData) { + try { if (typeof activityData.predefinedActivity.drawingData === 'string') activityData.predefinedActivity.drawingData = JSON.parse(activityData.predefinedActivity.drawingData); } catch (err) { console.error(err); } + } else if (firstImage && firstImage.drawingData) { + try { activityData.predefinedActivity.drawingData = JSON.parse(firstImage.drawingData); } catch (err) { console.error(err); } } - return imgData; - }); - - // Setze images Array - activityData.predefinedActivity.images = imagesWithParsedData; - - const firstImage = allImages.length > 0 ? allImages[0] : null; - - // Füge Zeichnungsdaten hinzu, falls vorhanden - // Priorität: 1. drawingData direkt auf PredefinedActivity, 2. drawingData aus firstImage - if (activityData.predefinedActivity.drawingData) { - // drawingData ist bereits vorhanden (aus dem Model) - try { - if (typeof activityData.predefinedActivity.drawingData === 'string') { - activityData.predefinedActivity.drawingData = JSON.parse(activityData.predefinedActivity.drawingData); - } - } catch (error) { - console.error(`Activity ${activityData.predefinedActivity.id}: Error parsing drawingData:`, error); + if (firstImage) { + activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`; + activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`; + } else { + activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`; + activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`; } - } else if (firstImage && firstImage.drawingData) { - try { - activityData.predefinedActivity.drawingData = JSON.parse(firstImage.drawingData); - } catch (error) { - console.error(`Activity ${activityData.predefinedActivity.id}: Error parsing drawingData from image:`, error); - } - } - - if (firstImage) { - // Füge sowohl imageUrl als auch imageLink mit Image-ID hinzu - activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`; - activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`; - } else { - // Fallback: Verwende den Basis-Pfad ohne Image-ID - activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`; - activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`; } } - - // Auch für GroupActivities - if (activityData.groupActivities && activityData.groupActivities.length > 0) { - const seenGroupActivityIds = new Set(); - activityData.groupActivities = activityData.groupActivities.filter(groupActivity => { - if (!groupActivity || groupActivity.id === undefined || groupActivity.id === null) { - return false; - } - if (seenGroupActivityIds.has(groupActivity.id)) { - return false; - } - seenGroupActivityIds.add(groupActivity.id); - return true; - }); - for (const groupActivity of activityData.groupActivities) { - if (groupActivity.groupPredefinedActivity) { - // Hole alle Images aus der Datenbank - const allImages = await PredefinedActivityImage.findAll({ - where: { predefinedActivityId: groupActivity.groupPredefinedActivity.id }, - order: [['createdAt', 'ASC']] - }); - - // Konvertiere Images zu JSON und parse drawingData falls vorhanden - const imagesWithParsedData = allImages.map(img => { - const imgData = img.toJSON(); - if (imgData.drawingData) { - try { - imgData.drawingData = JSON.parse(imgData.drawingData); - } catch (error) { - console.error(`Image ${imgData.id}: Error parsing drawingData:`, error); - } - } - return imgData; - }); - - // Setze images Array - groupActivity.groupPredefinedActivity.images = imagesWithParsedData; - - const firstImage = allImages.length > 0 ? allImages[0] : null; - - // Füge Zeichnungsdaten hinzu, falls vorhanden - // Priorität: 1. drawingData direkt auf PredefinedActivity, 2. drawingData aus firstImage - if (groupActivity.groupPredefinedActivity.drawingData) { - try { - if (typeof groupActivity.groupPredefinedActivity.drawingData === 'string') { - groupActivity.groupPredefinedActivity.drawingData = JSON.parse(groupActivity.groupPredefinedActivity.drawingData); - } - } catch (error) { - console.error(`GroupActivity ${groupActivity.groupPredefinedActivity.id}: Error parsing drawingData:`, error); - } - } else if (firstImage && firstImage.drawingData) { - try { - groupActivity.groupPredefinedActivity.drawingData = JSON.parse(firstImage.drawingData); - } catch (error) { - console.error(`GroupActivity ${groupActivity.groupPredefinedActivity.id}: Error parsing drawingData from image:`, error); - } - } - - if (firstImage) { - groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`; - groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`; + + // Load groupActivities separately + const groupActivities = await GroupActivity.findAll({ + where: { diaryDateActivity: activityData.id }, + order: [['orderId', 'ASC'], ['id', 'ASC']] + }); + const groupActivitiesData = []; + for (const ga of groupActivities) { + const gad = ga.toJSON(); + if (gad.groupId) { + const g = await Group.findByPk(gad.groupId); + gad.groupsGroupActivity = g ? g.toJSON() : null; + } + if (gad.customActivity) { + const gp = await PredefinedActivity.findByPk(gad.customActivity); + if (gp) { + const gpJson = gp.toJSON(); + const gpImages = await PredefinedActivityImage.findAll({ where: { predefinedActivityId: gpJson.id }, order: [['createdAt','ASC']] }); + gpJson.images = gpImages.map(i => { const ii = i.toJSON(); try { if (ii.drawingData) ii.drawingData = JSON.parse(ii.drawingData); } catch (e){}; return ii; }); + const firstImg = gpImages[0]; + if (firstImg) { + gpJson.imageUrl = `/api/predefined-activities/${gpJson.id}/image/${firstImg.id}`; + gpJson.imageLink = `/api/predefined-activities/${gpJson.id}/image/${firstImg.id}`; } else { - groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`; - groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`; + gpJson.imageUrl = `/api/predefined-activities/${gpJson.id}/image`; + gpJson.imageLink = `/api/predefined-activities/${gpJson.id}/image`; } + gad.groupPredefinedActivity = gpJson; } } + groupActivitiesData.push(gad); } - return activityData; - })); + activityData.groupActivities = groupActivitiesData; + + activitiesWithImages.push(activityData); + } return activitiesWithImages; } diff --git a/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt b/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt index 7f41e9c9..b9a12856 100644 --- a/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt +++ b/mobile-app/composeApp/src/androidMain/kotlin/de/tsschulz/tt_tagebuch/app/ui/AppRoot.kt @@ -5804,7 +5804,21 @@ private fun SettingsScreen( .navigationBarsPadding() .padding(horizontal = ScreenHorizontalPadding, vertical = 16.dp), ) { + Button( + onClick = { + dependencies.applicationScope.launch { + dependencies.clubManager.clearSelection() + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 12.dp) + .heightIn(min = 56.dp), + ) { + Text(tr("club.change", "Verein wechseln"), style = MaterialTheme.typography.h6) + } Header(tr("mobile.more", "Mehr")) + DetailLine(tr("mobile.user", "Benutzer"), authState.username ?: "-") DetailLine("Backend", dependencies.apiConfig.baseUrl) DetailLine("Club", clubState.clubs.firstOrNull { it.id == clubState.currentClubId }?.name ?: "-")