feat(Calendar): integrate CalendarEvent model and enhance calendar functionality
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 43s

- Added CalendarEvent model to the backend, establishing relationships with the Club model for better event management.
- Updated server.js to include calendarEventRoutes, enabling API access for calendar events.
- Enhanced CalendarView.vue to support custom event creation and management, improving user interaction with the calendar.
- Refactored various components to streamline event handling and improve overall user experience in the calendar interface.
- Updated TODO and DEVELOPMENT documentation to reflect new calendar features and architectural decisions.
This commit is contained in:
Torsten Schulz (local)
2026-05-13 10:21:30 +02:00
parent 9be5f50ede
commit 004801b1a6
33 changed files with 2715 additions and 632 deletions

View File

@@ -0,0 +1,59 @@
import { Op } from 'sequelize';
import CalendarEvent from '../models/CalendarEvent.js';
import { checkAccess } from '../utils/userUtils.js';
import HttpError from '../exceptions/HttpError.js';
class CalendarEventService {
async listClubEvents(userToken, clubId, year) {
await checkAccess(userToken, clubId);
const normalizedYear = this.normalizeYear(year);
return await CalendarEvent.findAll({
where: {
clubId,
startDate: { [Op.lte]: `${normalizedYear}-12-31` },
endDate: { [Op.gte]: `${normalizedYear}-01-01` },
},
order: [['startDate', 'ASC'], ['title', 'ASC']],
});
}
async createClubEvent(userToken, clubId, payload) {
await checkAccess(userToken, clubId);
const title = String(payload?.title || '').trim();
if (!title) throw new HttpError('Titel fehlt', 400);
const startDate = this.normalizeDate(payload?.startDate);
const endDate = this.normalizeDate(payload?.endDate || payload?.startDate);
if (!startDate || !endDate) throw new HttpError('Ungültiges Datum', 400);
if (startDate > endDate) throw new HttpError('Enddatum darf nicht vor dem Startdatum liegen', 400);
return await CalendarEvent.create({
clubId,
title,
startDate,
endDate,
category: payload?.category ? String(payload.category).trim().slice(0, 64) : null,
notes: payload?.notes ? String(payload.notes).trim() : null,
});
}
async deleteClubEvent(userToken, clubId, eventId) {
await checkAccess(userToken, clubId);
const event = await CalendarEvent.findOne({ where: { id: eventId, clubId } });
if (!event) throw new HttpError('Event nicht gefunden', 404);
await event.destroy();
return { success: true };
}
normalizeYear(year) {
const parsed = Number.parseInt(year, 10);
if (Number.isInteger(parsed) && parsed >= 2020 && parsed <= 2100) return parsed;
return new Date().getFullYear();
}
normalizeDate(date) {
const text = String(date || '').slice(0, 10);
return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : null;
}
}
export default new CalendarEventService();