diff --git a/controllers/liturgicalDayController.js b/controllers/liturgicalDayController.js new file mode 100644 index 0000000..6db04be --- /dev/null +++ b/controllers/liturgicalDayController.js @@ -0,0 +1,154 @@ +const { LiturgicalDay } = require('../models'); +const { Op } = require('sequelize'); +const axios = require('axios'); + +// Alle liturgischen Tage abrufen +const getAllLiturgicalDays = async (req, res) => { + try { + const days = await LiturgicalDay.findAll({ + order: [['date', 'ASC']] + }); + res.status(200).json(days); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Abrufen der liturgischen Tage' }); + } +}; + +// Eindeutige Namen für Multiselect abrufen +const getLiturgicalDayNames = async (req, res) => { + try { + const days = await LiturgicalDay.findAll({ + attributes: ['dayName'], + group: ['dayName'], + order: [['dayName', 'ASC']] + }); + const names = days.map(day => day.dayName); + res.status(200).json(names); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Abrufen der Tag-Namen' }); + } +}; + +// HTML von liturgischem Kalender parsen und in DB speichern +const loadLiturgicalYear = async (req, res) => { + const { year } = req.body; + + if (!year) { + return res.status(400).json({ message: 'Jahr ist erforderlich' }); + } + + const currentYear = new Date().getFullYear(); + if (year < currentYear || year > currentYear + 2) { + return res.status(400).json({ message: 'Jahr muss zwischen aktuellem Jahr und 2 Jahren in der Zukunft liegen' }); + } + + try { + const url = `https://www.eike-fleer.de/liturgischer-kalender/${year}.htm`; + const response = await axios.get(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + } + }); + + const html = response.data; + + // Parse HTML - suche nach Tabellenzeilen mit Datum und Name + // Format: "DD.MM.YYYY       DayName" + const regex = /(\d{2}\.\d{2}\.\d{4})\s*(?: |\s)+(.+?)(?:<\/|$)/gi; + const matches = [...html.matchAll(regex)]; + + const liturgicalDays = []; + + for (const match of matches) { + const dateStr = match[1]; // DD.MM.YYYY + let dayName = match[2]; + + // Bereinige den Tag-Namen von HTML-Tags und Entities + dayName = dayName + .replace(/<[^>]*>/g, '') // Entferne HTML-Tags + .replace(/ /g, ' ') // Ersetze   + .replace(/ä/g, 'ä') + .replace(/ö/g, 'ö') + .replace(/ü/g, 'ü') + .replace(/Ä/g, 'Ä') + .replace(/Ö/g, 'Ö') + .replace(/Ü/g, 'Ü') + .replace(/ß/g, 'ß') + .trim(); + + // Konvertiere Datum von DD.MM.YYYY zu YYYY-MM-DD + const [day, month, yearPart] = dateStr.split('.'); + const isoDate = `${yearPart}-${month}-${day}`; + + if (dayName && dayName.length > 0) { + liturgicalDays.push({ + date: isoDate, + dayName: dayName + }); + } + } + + if (liturgicalDays.length === 0) { + return res.status(500).json({ message: 'Keine liturgischen Tage gefunden. Möglicherweise hat sich das HTML-Format geändert.' }); + } + + // Speichere oder aktualisiere die Einträge + for (const day of liturgicalDays) { + await LiturgicalDay.upsert({ + date: day.date, + dayName: day.dayName + }); + } + + res.status(200).json({ + message: `${liturgicalDays.length} liturgische Tage für ${year} erfolgreich geladen`, + count: liturgicalDays.length + }); + } catch (error) { + console.error('Fehler beim Laden der liturgischen Tage:', error); + if (error.response && error.response.status === 404) { + return res.status(404).json({ message: `Liturgischer Kalender für ${year} nicht gefunden` }); + } + res.status(500).json({ message: 'Fehler beim Laden der liturgischen Tage', error: error.message }); + } +}; + +// Einzelnen Tag erstellen +const createLiturgicalDay = async (req, res) => { + try { + const day = await LiturgicalDay.create(req.body); + res.status(201).json(day); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Erstellen des liturgischen Tags' }); + } +}; + +// Tag löschen +const deleteLiturgicalDay = async (req, res) => { + try { + const { id } = req.params; + const deleted = await LiturgicalDay.destroy({ + where: { id } + }); + if (deleted) { + res.status(200).json({ message: 'Liturgischer Tag erfolgreich gelöscht' }); + } else { + res.status(404).json({ message: 'Liturgischer Tag nicht gefunden' }); + } + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Löschen des liturgischen Tags' }); + } +}; + +module.exports = { + getAllLiturgicalDays, + getLiturgicalDayNames, + loadLiturgicalYear, + createLiturgicalDay, + deleteLiturgicalDay +}; + diff --git a/migrations/20251007150137-create-liturgical-days.js b/migrations/20251007150137-create-liturgical-days.js new file mode 100644 index 0000000..49fe1fd --- /dev/null +++ b/migrations/20251007150137-create-liturgical-days.js @@ -0,0 +1,28 @@ +'use strict'; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up (queryInterface, Sequelize) { + await queryInterface.createTable('liturgical_days', { + id: { + type: Sequelize.INTEGER, + primaryKey: true, + autoIncrement: true, + allowNull: false + }, + date: { + type: Sequelize.DATEONLY, + allowNull: false, + unique: true + }, + dayName: { + type: Sequelize.STRING, + allowNull: false + } + }); + }, + + async down (queryInterface, Sequelize) { + await queryInterface.dropTable('liturgical_days'); + } +}; diff --git a/models/LiturgicalDay.js b/models/LiturgicalDay.js new file mode 100644 index 0000000..d89ba45 --- /dev/null +++ b/models/LiturgicalDay.js @@ -0,0 +1,24 @@ +module.exports = (sequelize, DataTypes) => { + const LiturgicalDay = sequelize.define('LiturgicalDay', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + date: { + type: DataTypes.DATEONLY, + allowNull: false, + unique: true + }, + dayName: { + type: DataTypes.STRING, + allowNull: false + } + }, { + tableName: 'liturgical_days', + timestamps: false + }); + + return LiturgicalDay; +}; + diff --git a/routes/liturgicalDays.js b/routes/liturgicalDays.js new file mode 100644 index 0000000..6c1ccce --- /dev/null +++ b/routes/liturgicalDays.js @@ -0,0 +1,19 @@ +const express = require('express'); +const router = express.Router(); +const { + getAllLiturgicalDays, + getLiturgicalDayNames, + loadLiturgicalYear, + createLiturgicalDay, + deleteLiturgicalDay +} = require('../controllers/liturgicalDayController'); +const authMiddleware = require('../middleware/authMiddleware'); + +router.get('/', getAllLiturgicalDays); +router.get('/names', getLiturgicalDayNames); +router.post('/load-year', authMiddleware, loadLiturgicalYear); +router.post('/', authMiddleware, createLiturgicalDay); +router.delete('/:id', authMiddleware, deleteLiturgicalDay); + +module.exports = router; + diff --git a/server.js b/server.js index c69645c..d4eb563 100644 --- a/server.js +++ b/server.js @@ -17,7 +17,8 @@ const worshipRouter = require('./routes/worships'); const pageRouter = require('./routes/pages'); const userRouter = require('./routes/users'); const imageRouter = require('./routes/image'); -const filesRouter = require('./routes/files'); +const filesRouter = require('./routes/files'); +const liturgicalDaysRouter = require('./routes/liturgicalDays'); const app = express(); const PORT = parseInt(process.env.PORT, 10) || 3000; @@ -56,6 +57,7 @@ app.use('/api/page-content', pageRouter); app.use('/api/users', userRouter); app.use('/api/image', imageRouter); app.use('/api/files', filesRouter); +app.use('/api/liturgical-days', liturgicalDaysRouter); const options = { key: fs.readFileSync('server.key'), diff --git a/src/content/admin/WorshipManagement.vue b/src/content/admin/WorshipManagement.vue index 5c5c6ad..71fe103 100644 --- a/src/content/admin/WorshipManagement.vue +++ b/src/content/admin/WorshipManagement.vue @@ -7,10 +7,29 @@ placeholder="Veranstaltungsort wählen"> - + - +
+ + +
+ + +
+
@@ -103,6 +122,7 @@ export default { name: 'WorshipManagement', components: { Multiselect }, data() { + const currentYear = new Date().getFullYear(); return { worships: [], eventPlaces: [], @@ -110,6 +130,12 @@ export default { sacristanOptions: [], selectedOrganizers: [], selectedSacristans: [], + dayNameOptions: [], + liturgicalDays: [], + selectedDayName: null, + selectedYear: currentYear, + availableYears: [currentYear, currentYear + 1, currentYear + 2], + isLoading: false, worshipData: { eventPlaceId: null, date: '', @@ -124,6 +150,7 @@ export default { introLine: '', sacristanService: '', website: '', + dayName: '', }, selectedEventPlace: null, editMode: false, @@ -173,6 +200,7 @@ export default { await this.fetchEventPlaces(); await this.fetchWorships(); await this.fetchWorshipOptions(); + await this.fetchLiturgicalDays(); }, methods: { formatTime, @@ -202,13 +230,62 @@ export default { console.error('Fehler beim Abrufen der Worship-Optionen:', error); } }, + async fetchLiturgicalDays() { + try { + const response = await axios.get('/liturgical-days'); + this.liturgicalDays = response.data; + + // Erstelle Optionen für Multiselect + const uniqueNames = [...new Set(response.data.map(day => day.dayName))]; + this.dayNameOptions = uniqueNames.sort().map(name => ({ name })); + } catch (error) { + console.error('Fehler beim Abrufen der liturgischen Tage:', error); + } + }, + async loadLiturgicalYear() { + if (!this.selectedYear) { + alert('Bitte wählen Sie ein Jahr aus'); + return; + } + + this.isLoading = true; + try { + const response = await axios.post('/liturgical-days/load-year', { + year: this.selectedYear + }); + alert(response.data.message); + await this.fetchLiturgicalDays(); + } catch (error) { + console.error('Fehler beim Laden des Kirchenjahres:', error); + if (error.response && error.response.data && error.response.data.message) { + alert('Fehler: ' + error.response.data.message); + } else { + alert('Fehler beim Laden des Kirchenjahres'); + } + } finally { + this.isLoading = false; + } + }, + updateDayNameFromDate() { + if (!this.worshipData.date) { + return; + } + + // Finde liturgischen Tag für das gewählte Datum + const liturgicalDay = this.liturgicalDays.find(day => day.date === this.worshipData.date); + if (liturgicalDay) { + this.selectedDayName = { name: liturgicalDay.dayName }; + this.worshipData.dayName = liturgicalDay.dayName; + } + }, async saveWorship() { try { const payload = { ...this.worshipData, eventPlaceId: this.selectedEventPlace ? this.selectedEventPlace.id : null, organizer: this.selectedOrganizers.map(org => org.name).join(', '), - sacristanService: this.selectedSacristans.map(sac => sac.name).join(', ') + sacristanService: this.selectedSacristans.map(sac => sac.name).join(', '), + dayName: this.selectedDayName ? this.selectedDayName.name : '' }; if (this.editMode) { @@ -239,6 +316,9 @@ export default { ? worship.sacristanService.split(',').map(sac => ({ name: sac.trim() })) : []; + // Setze dayName + this.selectedDayName = worship.dayName ? { name: worship.dayName } : null; + this.editMode = true; this.editId = worship.id; }, @@ -262,11 +342,13 @@ export default { selfInformation: false, highlightTime: false, neighborInvitation: false, - introLine: '' + introLine: '', + dayName: '' }; this.selectedEventPlace = null; this.selectedOrganizers = []; this.selectedSacristans = []; + this.selectedDayName = null; this.editMode = false; this.editId = null; }, @@ -291,6 +373,11 @@ export default { const tag = { name: newTag }; this.sacristanOptions.push(tag); this.selectedSacristans.push(tag); + }, + addDayNameTag(newTag) { + const tag = { name: newTag }; + this.dayNameOptions.push(tag); + this.selectedDayName = tag; } } }; @@ -373,6 +460,52 @@ button { background-color: #d32f2f; } +.liturgical-day-section { + display: flex; + flex-direction: column; + gap: 10px; +} + +.liturgical-loader { + display: flex; + gap: 10px; + align-items: center; +} + +.year-select { + padding: 8px 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 14px; + background-color: white; + cursor: pointer; +} + +.year-select:focus { + outline: none; + border-color: #4CAF50; +} + +.load-year-button { + padding: 8px 16px; + background-color: #2196F3; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + white-space: nowrap; + margin: 0; +} + +.load-year-button:hover:not(:disabled) { + background-color: #1976D2; +} + +.load-year-button:disabled { + background-color: #ccc; + cursor: not-allowed; +} + ul { margin-top: 20px; }