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.

This commit is contained in:
Torsten Schulz (local)
2026-01-29 16:57:12 +01:00
parent 8d2db95540
commit c09159d6ce
8 changed files with 180 additions and 15 deletions

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));

View File

@@ -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<Array<{ id: number, label: string, endpoint: string, description: string|null, orderId: number }>>}
*/
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 }> }>}

View File

@@ -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;

View File

@@ -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);