feat: improve user token handling and add club selection clearing button in settings
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ?: "-")
|
||||
|
||||
Reference in New Issue
Block a user