diff --git a/backend/controllers/memberController.js b/backend/controllers/memberController.js index 732384f..04bfa98 100644 --- a/backend/controllers/memberController.js +++ b/backend/controllers/memberController.js @@ -4,7 +4,6 @@ const getClubMembers = async(req, res) => { try { const { authcode: userToken } = req.headers; const { id: clubId } = req.params; - console.log('[getClubMembers]', userToken, clubId); res.status(200).json(await MemberService.getClubMembers(userToken, clubId)); } catch(error) { console.log('[getClubMembers] - Error: ', error); @@ -37,4 +36,34 @@ const setClubMembers = async (req, res) => { res.status(addResult.status || 500).json(addResult.response); } -export { getClubMembers, getWaitingApprovals, setClubMembers }; \ No newline at end of file +const uploadMemberImage = async (req, res) => { + try { + const { clubId, memberId } = req.params; + const { authcode: userToken } = req.headers; + const result = await MemberService.uploadMemberImage(userToken, clubId, memberId, req.file.buffer); + res.status(result.status).json(result.message ? { message: result.message } : { error: result.error }); + } catch (error) { + console.error('[uploadMemberImage] - Error:', error); + res.status(500).json({ error: 'Failed to upload image' }); + } +}; + +const getMemberImage = async (req, res) => { + console.log('[getMemberImage]'); + try { + const { clubId, memberId } = req.params; + const { authcode: userToken } = req.headers; + console.log('-------------------->', clubId, memberId, userToken); + const result = await MemberService.getMemberImage(userToken, clubId, memberId); + if (result.status === 200) { + res.sendFile(result.imagePath); + } else { + res.status(result.status).json({ error: result.error }); + } + } catch (error) { + console.error('[getMemberImage] - Error:', error); + res.status(500).json({ error: 'Failed to retrieve image' }); + } +}; + +export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage }; \ No newline at end of file diff --git a/backend/images/members/9.jpg b/backend/images/members/9.jpg new file mode 100644 index 0000000..d3eaf5b Binary files /dev/null and b/backend/images/members/9.jpg differ diff --git a/backend/images/members/dummy b/backend/images/members/dummy new file mode 100644 index 0000000..e69de29 diff --git a/backend/routes/memberRoutes.js b/backend/routes/memberRoutes.js index b0e5010..0619353 100644 --- a/backend/routes/memberRoutes.js +++ b/backend/routes/memberRoutes.js @@ -1,9 +1,15 @@ -import { getClubMembers, getWaitingApprovals, setClubMembers } from '../controllers/memberController.js'; +import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage } from '../controllers/memberController.js'; import express from 'express'; import { authenticate } from '../middleware/authMiddleware.js'; +import multer from 'multer'; const router = express.Router(); +const storage = multer.memoryStorage(); +const upload = multer({ storage: storage }); + +router.post('/:clubId/image/:memberId', authenticate, upload.single('image'), uploadMemberImage); +router.get('/:clubId/image/:memberId', authenticate, getMemberImage); router.get('/:id', authenticate, getClubMembers); router.post('/:id', authenticate, setClubMembers); router.get('/notapproved/:id', authenticate, getWaitingApprovals); diff --git a/backend/services/memberService.js b/backend/services/memberService.js index 1325915..c6a2e24 100644 --- a/backend/services/memberService.js +++ b/backend/services/memberService.js @@ -1,9 +1,10 @@ import UserClub from "../models/UserClub.js"; import { checkAccess } from "../utils/userUtils.js"; import { getUserByToken } from "../utils/userUtils.js"; -import HttpError from "../exceptions/HttpError.js"; import Member from "../models/Member.js"; -import { response } from "express"; +import path from 'path'; +import fs from 'fs'; +import sharp from 'sharp'; class MemberService { async getApprovalRequests(userToken, clubId) { @@ -79,6 +80,45 @@ class MemberService { } } } + + async uploadMemberImage(userToken, clubId, memberId, imageBuffer) { + try { + console.log('------>', userToken, clubId, memberId, imageBuffer); + await checkAccess(userToken, clubId); + const member = await Member.findOne({ where: { id: memberId, clubId: clubId } }); + if (!member) { + return { status: 404, error: 'Member not found in this club' }; + } + const imagePath = path.join('images', 'members', `${memberId}.jpg`); + await sharp(imageBuffer) + .resize(600, 600) + .jpeg({ quality: 80 }) + .toFile(imagePath); + + return { status: 200, message: 'Image uploaded successfully' }; + } catch (error) { + console.error('[uploadMemberImage] - Error:', error); + return { status: 500, error: 'Failed to upload image' }; + } + } + + async getMemberImage(userToken, clubId, memberId) { + try { + await checkAccess(userToken, clubId); + const member = await Member.findOne({ where: { id: memberId, clubId: clubId } }); + if (!member) { + return { status: 404, error: 'Member not found in this club' }; + } + const imagePath = path.join('images', 'members', `${memberId}.jpg`); + if (!fs.existsSync(imagePath)) { + return { status: 404, error: 'Image not found' }; + } + return { status: 200, imagePath: path.resolve(imagePath) }; + } catch (error) { + console.error('[getMemberImage] - Error:', error); + return { status: 500, error: 'Failed to retrieve image' }; + } + } } export default new MemberService(); \ No newline at end of file diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 9f1e803..d48e615 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -123,20 +123,28 @@ @@ -339,8 +347,9 @@ export default { this.selectedActivityTags = []; } }, - openNotesModal(member) { + async openNotesModal(member) { this.selectedMember = member; + await this.loadMemberImage(member); this.loadMemberNotesAndTags(this.date.id, member.id); this.showNotesModal = true; }, @@ -608,6 +617,18 @@ export default { alert('Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut.'); } }, + async loadMemberImage(member) { + try { + const response = await apiClient.get(`/clubmembers/${this.currentClub}/image/${member.id}`, { + responseType: 'blob', + }); + const imageUrl = URL.createObjectURL(response.data); + member.imageUrl = imageUrl; + } catch (error) { + console.error("Failed to load member image:", error); + member.imageUrl = null; + } + } }, async mounted() { @@ -789,4 +810,21 @@ input[type="number"] { .drag-handle { cursor: pointer; } + +.modal-body { + display: flex; + justify-content: space-between; +} + +.modal-left { + flex: 0 0 250px; + display: flex; + justify-content: center; + align-items: center; + padding-right: 20px; +} + +.modal-right { + flex: 1; +} diff --git a/frontend/src/views/MembersView.vue b/frontend/src/views/MembersView.vue index dfe2e3f..8e693bd 100644 --- a/frontend/src/views/MembersView.vue +++ b/frontend/src/views/MembersView.vue @@ -2,8 +2,11 @@

Mitglieder

-
{{ memberFormIsOpen ? '-' : - '+' }}{{ memberToEdit === null ? "Neues Mitglied" : "Mitglied bearbeiten" }} +
+ + {{ memberFormIsOpen ? '-' : '+' }} + {{ memberToEdit === null ? "Neues Mitglied" : "Mitglied bearbeiten" }} +
@@ -15,8 +18,13 @@ + +
+ Vorschau des Mitgliedsbildes +
- +
@@ -25,6 +33,7 @@ + @@ -34,17 +43,31 @@ + - +
Bild Name, Vorname Adresse Geburtsdatum
+ Mitgliedsbild + {{ member.lastName }}, {{ member.firstName }} {{ member.street }}, {{ member.city }} {{ member.birthDate }} {{ member.phone }} {{ member.email }} + +
+ +