From c09159d6ce3cd4e433315745582c60b01723b002 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 29 Jan 2026 16:57:12 +0100 Subject: [PATCH] Add widget management functionality: Implement getAvailableWidgets method in dashboardService to retrieve widget types, and create corresponding API endpoint in dashboardRouter. Update LoggedInView to allow users to select and add widgets dynamically, enhancing dashboard customization options. --- backend/controllers/dashboardController.js | 11 ++++ backend/models/index.js | 2 + backend/models/type/widget_type.js | 39 ++++++++++++ backend/routers/dashboardRouter.js | 1 + backend/services/dashboardService.js | 19 ++++++ backend/utils/initializeWidgetTypes.js | 25 ++++++++ backend/utils/syncDatabase.js | 25 +++++++- frontend/src/views/home/LoggedInView.vue | 73 +++++++++++++++++----- 8 files changed, 180 insertions(+), 15 deletions(-) create mode 100644 backend/models/type/widget_type.js create mode 100644 backend/utils/initializeWidgetTypes.js diff --git a/backend/controllers/dashboardController.js b/backend/controllers/dashboardController.js index bc2a86d..905785a 100644 --- a/backend/controllers/dashboardController.js +++ b/backend/controllers/dashboardController.js @@ -5,6 +5,17 @@ function getHashedUserId(req) { } export default { + /** Liste der möglichen Widget-Typen (öffentlich, keine Auth nötig wenn gewünscht – aktuell mit Auth). */ + async getAvailableWidgets(req, res) { + try { + const list = await dashboardService.getAvailableWidgets(); + res.json(list); + } catch (error) { + console.error('Dashboard getAvailableWidgets:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); + } + }, + async getConfig(req, res) { const hashedUserId = getHashedUserId(req); if (!hashedUserId) { diff --git a/backend/models/index.js b/backend/models/index.js index 7d9845d..7073fd0 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -4,6 +4,7 @@ import SettingsType from './type/settings.js'; import UserParamValue from './type/user_param_value.js'; import UserParamType from './type/user_param.js'; import UserRightType from './type/user_right.js'; +import WidgetType from './type/widget_type.js'; import User from './community/user.js'; import UserParam from './community/user_param.js'; import UserDashboard from './community/user_dashboard.js'; @@ -151,6 +152,7 @@ const models = { UserParamValue, UserParamType, UserRightType, + WidgetType, User, UserParam, UserDashboard, diff --git a/backend/models/type/widget_type.js b/backend/models/type/widget_type.js new file mode 100644 index 0000000..0abe367 --- /dev/null +++ b/backend/models/type/widget_type.js @@ -0,0 +1,39 @@ +import { sequelize } from '../../utils/sequelize.js'; +import { DataTypes } from 'sequelize'; + +const WidgetType = sequelize.define('widget_type', { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true + }, + label: { + type: DataTypes.STRING, + allowNull: false, + comment: 'Anzeigename des Widgets (z. B. "Termine")' + }, + endpoint: { + type: DataTypes.STRING, + allowNull: false, + comment: 'API-Pfad (z. B. "/api/termine")' + }, + description: { + type: DataTypes.STRING, + allowNull: true, + comment: 'Optionale Beschreibung' + }, + orderId: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + field: 'order_id', + comment: 'Sortierreihenfolge' + } +}, { + tableName: 'widget_type', + schema: 'type', + underscored: true, + timestamps: false +}); + +export default WidgetType; diff --git a/backend/routers/dashboardRouter.js b/backend/routers/dashboardRouter.js index 3860722..41a99e0 100644 --- a/backend/routers/dashboardRouter.js +++ b/backend/routers/dashboardRouter.js @@ -4,6 +4,7 @@ import dashboardController from '../controllers/dashboardController.js'; const router = Router(); +router.get('/widgets', authenticate, dashboardController.getAvailableWidgets.bind(dashboardController)); router.get('/config', authenticate, dashboardController.getConfig.bind(dashboardController)); router.put('/config', authenticate, dashboardController.setConfig.bind(dashboardController)); diff --git a/backend/services/dashboardService.js b/backend/services/dashboardService.js index 940b82c..267bdd4 100644 --- a/backend/services/dashboardService.js +++ b/backend/services/dashboardService.js @@ -1,7 +1,26 @@ import BaseService from './BaseService.js'; import UserDashboard from '../models/community/user_dashboard.js'; +import WidgetType from '../models/type/widget_type.js'; class DashboardService extends BaseService { + /** + * Liste aller möglichen (verfügbaren) Widget-Typen. + * @returns {Promise>} + */ + async getAvailableWidgets() { + const rows = await WidgetType.findAll({ + order: [['orderId', 'ASC'], ['id', 'ASC']], + attributes: ['id', 'label', 'endpoint', 'description', 'orderId'] + }); + return rows.map(r => ({ + id: r.id, + label: r.label, + endpoint: r.endpoint, + description: r.description ?? null, + orderId: r.orderId + })); + } + /** * @param {string} hashedUserId * @returns {Promise<{ widgets: Array<{ id: string, title: string, endpoint: string }> }>} diff --git a/backend/utils/initializeWidgetTypes.js b/backend/utils/initializeWidgetTypes.js new file mode 100644 index 0000000..5ae1fb0 --- /dev/null +++ b/backend/utils/initializeWidgetTypes.js @@ -0,0 +1,25 @@ +import WidgetType from '../models/type/widget_type.js'; + +const DEFAULT_WIDGET_TYPES = [ + { label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 } +]; + +/** + * Stellt die Standard-Widget-Typen in type.widget_type bereit. + * Idempotent: vorhandene Einträge werden nicht verändert. + */ +const initializeWidgetTypes = async () => { + for (const row of DEFAULT_WIDGET_TYPES) { + await WidgetType.findOrCreate({ + where: { endpoint: row.endpoint }, + defaults: { + label: row.label, + endpoint: row.endpoint, + description: row.description ?? null, + orderId: row.orderId ?? 0 + } + }); + } +}; + +export default initializeWidgetTypes; diff --git a/backend/utils/syncDatabase.js b/backend/utils/syncDatabase.js index 95f82c2..66c0780 100644 --- a/backend/utils/syncDatabase.js +++ b/backend/utils/syncDatabase.js @@ -72,6 +72,7 @@ import initializeChat from './initializeChat.js'; import initializeMatch3Data from './initializeMatch3.js'; import updateExistingMatch3Levels from './updateExistingMatch3Levels.js'; import initializeTaxi from './initializeTaxi.js'; +import initializeWidgetTypes from './initializeWidgetTypes.js'; // Normale Synchronisation (nur bei STAGE=dev Schema-Updates) const syncDatabase = async () => { @@ -89,7 +90,23 @@ const syncDatabase = async () => { } console.log("Initializing database schemas..."); - await initializeDatabase(); + await initializeDatabase(); + + // Dashboard: Widget-Typen-Tabelle (mögliche Widgets) + console.log("Ensuring widget_type table exists..."); + try { + await sequelize.query(` + CREATE TABLE IF NOT EXISTS type.widget_type ( + id SERIAL PRIMARY KEY, + label VARCHAR(255) NOT NULL, + endpoint VARCHAR(255) NOT NULL, + description VARCHAR(255), + order_id INTEGER NOT NULL DEFAULT 0 + ); + `); + } catch (e) { + console.warn('⚠️ Konnte type.widget_type nicht anlegen:', e?.message || e); + } // Vokabeltrainer: Tabellen sicherstellen (auch ohne manuell ausgeführte Migrations) // Hintergrund: In Produktion sind Schema-Updates deaktiviert, und Migrations werden nicht automatisch ausgeführt. @@ -676,6 +693,9 @@ const syncDatabase = async () => { console.log("Initializing Taxi..."); await initializeTaxi(); + console.log("Initializing widget types..."); + await initializeWidgetTypes(); + console.log('Database synchronization complete.'); } catch (error) { console.error('Unable to synchronize the database:', error); @@ -1013,6 +1033,9 @@ const syncDatabaseForDeployment = async () => { console.log("Initializing Taxi..."); await initializeTaxi(); + console.log("Initializing widget types..."); + await initializeWidgetTypes(); + console.log('Database synchronization for deployment complete.'); } catch (error) { console.error('Unable to synchronize the database for deployment:', error); diff --git a/frontend/src/views/home/LoggedInView.vue b/frontend/src/views/home/LoggedInView.vue index 3018948..5bd1987 100644 --- a/frontend/src/views/home/LoggedInView.vue +++ b/frontend/src/views/home/LoggedInView.vue @@ -13,9 +13,22 @@ Dashboard bearbeiten