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