feat(member-notes): enhance member note functionality with structured data and observation tracking
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s

This commit is contained in:
Torsten Schulz (local)
2026-08-14 13:39:00 +02:00
parent f59c7391b8
commit 313790428d
7 changed files with 262 additions and 16 deletions

View File

@@ -17,8 +17,8 @@ const getMemberNotes = async (req, res) => {
const addMemberNote = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
const { memberId, content, clubId } = req.body;
await MemberNoteService.addNoteToMember(userToken, clubId, memberId, content);
const { memberId, content, clubId, kind, trainingFocus, sourceType, sourceLabel } = req.body;
await MemberNoteService.addNoteToMember(userToken, clubId, memberId, content, { kind, trainingFocus, sourceType, sourceLabel });
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
res.status(201).json(notes);
} catch (error) {

View File

@@ -22,6 +22,40 @@ const MemberNote = sequelize.define('MemberNote', {
return decryptData(encryptedValue);
}
},
kind: {
type: DataTypes.ENUM('general', 'strength', 'development', 'training_focus'),
allowNull: false,
defaultValue: 'general',
},
trainingFocus: {
type: DataTypes.TEXT,
allowNull: true,
field: 'training_focus',
set(value) {
this.setDataValue('trainingFocus', value ? encryptData(value) : null);
},
get() {
const encryptedValue = this.getDataValue('trainingFocus');
return encryptedValue ? decryptData(encryptedValue) : null;
}
},
sourceType: {
type: DataTypes.STRING,
allowNull: true,
field: 'source_type',
},
sourceLabel: {
type: DataTypes.TEXT,
allowNull: true,
field: 'source_label',
set(value) {
this.setDataValue('sourceLabel', value ? encryptData(value) : null);
},
get() {
const encryptedValue = this.getDataValue('sourceLabel');
return encryptedValue ? decryptData(encryptedValue) : null;
}
},
memberId: {
type: DataTypes.INTEGER,
allowNull: false,

View File

@@ -1,16 +1,34 @@
import MemberNote from '../models/MemberNote.js';
import Member from '../models/Member.js';
import { checkAccess } from '../utils/userUtils.js';
import { devLog } from '../utils/logger.js';
class MemberNoteService {
async addNoteToMember(userToken, clubId, memberId, content) {
async ensureMemberInClub(clubId, memberId) {
const member = await Member.findOne({ where: { id: memberId, clubId } });
if (!member) throw new Error('Member not found in club');
return member;
}
async addNoteToMember(userToken, clubId, memberId, content, metadata = {}) {
await checkAccess(userToken, clubId);
return await MemberNote.create({ memberId, content });
await this.ensureMemberInClub(clubId, memberId);
const allowedKinds = new Set(['general', 'strength', 'development', 'training_focus']);
const kind = allowedKinds.has(metadata.kind) ? metadata.kind : 'general';
return await MemberNote.create({
memberId,
content,
kind,
trainingFocus: metadata.trainingFocus || null,
sourceType: metadata.sourceType || null,
sourceLabel: metadata.sourceLabel || null,
});
}
async getNotesForMember(userToken, clubId, memberId) {
devLog(userToken, clubId);
await checkAccess(userToken, clubId);
await this.ensureMemberInClub(clubId, memberId);
return await MemberNote.findAll({
where: { memberId },
order: [['createdAt', 'DESC']]
@@ -23,6 +41,7 @@ class MemberNoteService {
if (!note) {
throw new Error('Note not found');
}
await this.ensureMemberInClub(clubId, note.memberId);
await note.destroy();
}
}