Fügt Unterstützung für vordefinierte Aktivitäten hinzu, einschließlich der Möglichkeit, Bilder hochzuladen und zu suchen. Aktualisiert die Datenbankmodelle und -routen entsprechend. Verbessert die Benutzeroberfläche zur Anzeige und Bearbeitung von Aktivitäten in DiaryView.vue.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
import predefinedActivityService from '../services/predefinedActivityService.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
export const createPredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, description, durationText, duration });
|
||||
const { name, code, description, durationText, duration, imageLink } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, code, description, durationText, duration, imageLink });
|
||||
res.status(201).json(predefinedActivity);
|
||||
} catch (error) {
|
||||
console.error('[createPredefinedActivity] - Error:', error);
|
||||
@@ -25,10 +28,11 @@ export const getPredefinedActivityById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const predefinedActivity = await predefinedActivityService.getPredefinedActivityById(id);
|
||||
const images = await PredefinedActivityImage.findAll({ where: { predefinedActivityId: id } });
|
||||
if (!predefinedActivity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
res.status(200).json(predefinedActivity);
|
||||
res.status(200).json({ ...predefinedActivity.toJSON(), images });
|
||||
} catch (error) {
|
||||
console.error('[getPredefinedActivityById] - Error:', error);
|
||||
res.status(500).json({ error: 'Error fetching predefined activity' });
|
||||
@@ -38,11 +42,22 @@ export const getPredefinedActivityById = async (req, res) => {
|
||||
export const updatePredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, description, durationText, duration } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, description, durationText, duration });
|
||||
const { name, code, description, durationText, duration, imageLink } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, code, description, durationText, duration, imageLink });
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.error('[updatePredefinedActivity] - Error:', error);
|
||||
res.status(500).json({ error: 'Error updating predefined activity' });
|
||||
}
|
||||
};
|
||||
|
||||
export const searchPredefinedActivities = async (req, res) => {
|
||||
try {
|
||||
const { q, limit } = req.query;
|
||||
const result = await predefinedActivityService.searchPredefinedActivities(q, limit);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('[searchPredefinedActivities] - Error:', error);
|
||||
res.status(500).json({ error: 'Error searching predefined activities' });
|
||||
}
|
||||
};
|
||||
|
||||
53
backend/controllers/predefinedActivityImageController.js
Normal file
53
backend/controllers/predefinedActivityImageController.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
|
||||
export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params; // predefinedActivityId
|
||||
const { authcode: userToken } = req.headers;
|
||||
await checkAccess(userToken); // Club-Kontext ist hier nicht zwingend, falls gewünscht kann erweitert werden
|
||||
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
return res.status(404).json({ error: 'Predefined activity not found' });
|
||||
}
|
||||
|
||||
if (!req.file || !req.file.buffer) {
|
||||
return res.status(400).json({ error: 'No image uploaded' });
|
||||
}
|
||||
|
||||
const imagesDir = path.join('images', 'predefined');
|
||||
if (!fs.existsSync(imagesDir)) {
|
||||
fs.mkdirSync(imagesDir, { recursive: true });
|
||||
}
|
||||
|
||||
const fileName = `${id}-${Date.now()}.jpg`;
|
||||
const filePath = path.join(imagesDir, fileName);
|
||||
|
||||
await sharp(req.file.buffer)
|
||||
.resize(800, 800, { fit: 'inside' })
|
||||
.jpeg({ quality: 85 })
|
||||
.toFile(filePath);
|
||||
|
||||
const imageRecord = await PredefinedActivityImage.create({
|
||||
predefinedActivityId: id,
|
||||
imagePath: filePath,
|
||||
mimeType: 'image/jpeg',
|
||||
});
|
||||
|
||||
// Optional: als imageLink am Activity-Datensatz setzen
|
||||
activity.imageLink = `/api/predefined-activities/${id}/image/${imageRecord.id}`;
|
||||
await activity.save();
|
||||
|
||||
res.status(201).json({ id: imageRecord.id, imageLink: activity.imageLink });
|
||||
} catch (error) {
|
||||
console.error('[uploadPredefinedActivityImage] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to upload image' });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
@@ -23,6 +27,10 @@ const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
imageLink: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activities',
|
||||
timestamps: true,
|
||||
|
||||
30
backend/models/PredefinedActivityImage.js
Normal file
30
backend/models/PredefinedActivityImage.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const PredefinedActivityImage = sequelize.define('PredefinedActivityImage', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
predefinedActivityId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
imagePath: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
mimeType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activity_images',
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
});
|
||||
|
||||
export default PredefinedActivityImage;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import DiaryDateTag from './DiaryDateTag.js';
|
||||
import DiaryMemberNote from './DiaryMemberNote.js';
|
||||
import DiaryMemberTag from './DiaryMemberTag.js';
|
||||
import PredefinedActivity from './PredefinedActivity.js';
|
||||
import PredefinedActivityImage from './PredefinedActivityImage.js';
|
||||
import DiaryDateActivity from './DiaryDateActivity.js';
|
||||
import Match from './Match.js';
|
||||
import League from './League.js';
|
||||
@@ -76,6 +77,9 @@ DiaryDateActivity.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDa
|
||||
|
||||
PredefinedActivity.hasMany(DiaryDateActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivities' });
|
||||
DiaryDateActivity.belongsTo(PredefinedActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivity' });
|
||||
// PredefinedActivity Images
|
||||
PredefinedActivity.hasMany(PredefinedActivityImage, { foreignKey: 'predefinedActivityId', as: 'images' });
|
||||
PredefinedActivityImage.belongsTo(PredefinedActivity, { foreignKey: 'predefinedActivityId', as: 'predefinedActivity' });
|
||||
|
||||
Club.hasMany(Match, { foreignKey: 'clubId', as: 'matches' });
|
||||
Match.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
@@ -198,6 +202,7 @@ export {
|
||||
DiaryMemberNote,
|
||||
DiaryMemberTag,
|
||||
PredefinedActivity,
|
||||
PredefinedActivityImage,
|
||||
DiaryDateActivity,
|
||||
Match,
|
||||
League,
|
||||
|
||||
@@ -4,13 +4,35 @@ import {
|
||||
getAllPredefinedActivities,
|
||||
getPredefinedActivityById,
|
||||
updatePredefinedActivity,
|
||||
searchPredefinedActivities,
|
||||
} from '../controllers/predefinedActivityController.js';
|
||||
import multer from 'multer';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { uploadPredefinedActivityImage } from '../controllers/predefinedActivityImageController.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
const router = express.Router();
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
|
||||
router.post('/', createPredefinedActivity);
|
||||
router.get('/', getAllPredefinedActivities);
|
||||
router.get('/:id', getPredefinedActivityById);
|
||||
router.put('/:id', updatePredefinedActivity);
|
||||
router.post('/', authenticate, createPredefinedActivity);
|
||||
router.get('/', authenticate, getAllPredefinedActivities);
|
||||
router.get('/:id', authenticate, getPredefinedActivityById);
|
||||
router.put('/:id', authenticate, updatePredefinedActivity);
|
||||
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.get('/search/query', authenticate, searchPredefinedActivities);
|
||||
router.get('/:id/image/:imageId', authenticate, async (req, res) => {
|
||||
try {
|
||||
const { id, imageId } = req.params;
|
||||
const image = await PredefinedActivityImage.findOne({ where: { id: imageId, predefinedActivityId: id } });
|
||||
if (!image) return res.status(404).json({ error: 'Image not found' });
|
||||
if (!fs.existsSync(image.imagePath)) return res.status(404).json({ error: 'Image file missing' });
|
||||
res.sendFile(path.resolve(image.imagePath));
|
||||
} catch (e) {
|
||||
console.error('[getPredefinedActivityImage] - Error:', e);
|
||||
res.status(500).json({ error: 'Failed to fetch image' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -6,7 +6,7 @@ import cors from 'cors';
|
||||
import {
|
||||
User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, DiaryDateActivity, Match, League, Team, Group,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, Match, League, Team, Group,
|
||||
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
||||
TournamentMember, Accident, UserToken
|
||||
} from './models/index.js';
|
||||
@@ -88,6 +88,7 @@ app.get('*', (req, res) => {
|
||||
await DiaryMemberTag.sync({ alter: true });
|
||||
await DiaryMemberNote.sync({ alter: true });
|
||||
await PredefinedActivity.sync({ alter: true });
|
||||
await PredefinedActivityImage.sync({ alter: true });
|
||||
await DiaryDateActivity.sync({ alter: true });
|
||||
await Season.sync({ alter: true });
|
||||
await League.sync({ alter: true });
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
class PredefinedActivityService {
|
||||
async createPredefinedActivity(data) {
|
||||
console.log('[PredefinedActivityService::createPredefinedActivity] - Creating predefined activity');
|
||||
return await PredefinedActivity.create({
|
||||
name: data.name,
|
||||
code: data.code,
|
||||
description: data.description,
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
imageLink: data.imageLink,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,9 +23,11 @@ class PredefinedActivityService {
|
||||
}
|
||||
return await activity.update({
|
||||
name: data.name,
|
||||
code: data.code,
|
||||
description: data.description,
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
imageLink: data.imageLink,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,6 +45,23 @@ class PredefinedActivityService {
|
||||
}
|
||||
return activity;
|
||||
}
|
||||
|
||||
async searchPredefinedActivities(query, limit = 20) {
|
||||
const q = (query || '').trim();
|
||||
if (!q || q.length < 2) {
|
||||
return [];
|
||||
}
|
||||
return await PredefinedActivity.findAll({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ name: { [Op.like]: `%${q}%` } },
|
||||
{ code: { [Op.like]: `%${q}%` } },
|
||||
],
|
||||
},
|
||||
order: [['name', 'ASC']],
|
||||
limit: Math.min(parseInt(limit || 20, 10), 50),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new PredefinedActivityService();
|
||||
|
||||
Reference in New Issue
Block a user