# WebUntis Event Type Implementation

**Date**: 2025-10-19  
**Status**: Completed

## Summary

Implemented support for a new `webuntis` event type that displays a centrally-configured WebUntis website on infoscreen clients. This event type follows the same client-side behavior as `website` events but sources its URL from system settings rather than per-event configuration.

## Changes Made

### 1. Database & Models

The `webuntis` event type was already defined in the `EventType` enum in `models/models.py`:

```python
class EventType(enum.Enum):
    presentation = "presentation"
    website = "website"
    video = "video"
    message = "message"
    other = "other"
    webuntis = "webuntis"  # Already present
```

### 2. System Settings

#### Default Initialization (`server/init_defaults.py`)

Updated `supplement_table_url` description to indicate it's used for both Vertretungsplan and WebUntis:

```python
('supplement_table_url', '', 'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)')
```

This setting is automatically seeded during database initialization.

**Note**: The same URL (`supplement_table_url`) is used for both:
- Vertretungsplan (supplement table) displays
- WebUntis event displays

#### API Endpoints (`server/routes/system_settings.py`)

WebUntis events use the existing supplement table endpoints:

- **`GET /api/system-settings/supplement-table`** (Admin+)
  - Returns: `{"url": "https://...", "enabled": true/false}`
  
- **`POST /api/system-settings/supplement-table`** (Admin+)
  - Body: `{"url": "https://...", "enabled": true/false}`
  - Updates the URL used for both supplement table and WebUntis events

No separate WebUntis URL endpoint is needed—the supplement table URL serves both purposes.

### 3. Event Creation (`server/routes/events.py`)

Added handling for `webuntis` event type in `create_event()`:

```python
# WebUntis: URL aus System-Einstellungen holen und EventMedia anlegen
if event_type == "webuntis":
    # Hole WebUntis-URL aus Systemeinstellungen (verwendet supplement_table_url)
    webuntis_setting = session.query(SystemSetting).filter_by(key='supplement_table_url').first()
    webuntis_url = webuntis_setting.value if webuntis_setting else ''
    
    if not webuntis_url:
        return jsonify({"error": "WebUntis / Supplement table URL not configured in system settings"}), 400
    
    # EventMedia für WebUntis anlegen
    media = EventMedia(
        media_type=MediaType.website,
        url=webuntis_url,
        file_path=webuntis_url
    )
    session.add(media)
    session.commit()
    event_media_id = media.id
```

**Workflow**:
1. Check if `supplement_table_url` is configured in system settings
2. Return error if not configured
3. Create `EventMedia` with `MediaType.website` using the supplement table URL
4. Associate the media with the event

### 4. Scheduler Payload (`scheduler/db_utils.py`)

Modified `format_event_with_media()` to handle both `website` and `webuntis` events:

```python
# Handle website and webuntis events (both display a website)
elif event.event_type.value in ("website", "webuntis"):
    event_dict["website"] = {
        "type": "browser",
        "url": media.url if media.url else None
    }
    if media.id not in _media_decision_logged:
        logging.debug(
            f"[Scheduler] Using website URL for event_media_id={media.id} (type={event.event_type.value}): {media.url}")
        _media_decision_logged.add(media.id)
```

**Key Points**:
- Both event types use the same `website` payload structure
- Clients interpret `event_type` but handle display identically
- URL is already resolved from system settings during event creation

### 5. Documentation

Created comprehensive documentation in `MQTT_EVENT_PAYLOAD_GUIDE.md` covering:
- MQTT message structure
- Event type-specific payloads
- Best practices for client implementation
- Versioning strategy
- System settings integration

## MQTT Message Format

### WebUntis Event Payload

```json
{
  "id": 125,
  "event_type": "webuntis",
  "title": "Schedule Display",
  "start": "2025-10-19T09:00:00+00:00",
  "end": "2025-10-19T09:30:00+00:00",
  "group_id": 1,
  "website": {
    "type": "browser",
    "url": "https://webuntis.example.com/schedule"
  }
}
```

### Website Event Payload (for comparison)

```json
{
  "id": 124,
  "event_type": "website",
  "title": "School Website",
  "start": "2025-10-19T09:00:00+00:00",
  "end": "2025-10-19T09:30:00+00:00",
  "group_id": 1,
  "website": {
    "type": "browser",
    "url": "https://example.com/page"
  }
}
```

## Client Implementation Guide

Clients should handle both `website` and `webuntis` event types identically:

```javascript
function parseEvent(event) {
  switch (event.event_type) {
    case 'presentation':
      return handlePresentation(event.presentation);
    
    case 'website':
    case 'webuntis':
      // Both types use the same display logic
      return handleWebsite(event.website);
    
    case 'video':
      return handleVideo(event.video);
    
    default:
      console.warn(`Unknown event type: ${event.event_type}`);
  }
}

function handleWebsite(websiteData) {
  // websiteData = { type: "browser", url: "https://..." }
  if (!websiteData.url) {
    console.error('Website event missing URL');
    return;
  }
  
  // Display URL in embedded browser/webview
  displayInBrowser(websiteData.url);
}
```

## Best Practices

### 1. Type-Based Dispatch
Always check `event_type` first and dispatch to appropriate handlers. The nested payload structure (`presentation`, `website`, etc.) provides type-specific details.

### 2. Graceful Error Handling
- Validate URLs before displaying
- Handle missing or empty URLs gracefully
- Provide user-friendly error messages

### 3. Unified Website Display
Both `website` and `webuntis` events trigger the same browser/webview component. The only difference is in event creation (per-event URL vs. system-wide URL).

### 4. Extensibility
The message structure supports adding new event types without breaking existing clients:
- Old clients ignore unknown `event_type` values
- New fields in existing payloads are optional
- Nested objects isolate type-specific changes

## Administrative Setup

### Setting the WebUntis / Supplement Table URL

The same URL is used for both Vertretungsplan (supplement table) and WebUntis displays.

1. **Via API** (recommended for UI integration):
   ```bash
   POST /api/system-settings/supplement-table
   {
     "url": "https://webuntis.example.com/schedule",
     "enabled": true
   }
   ```

2. **Via Database** (for initial setup):
   ```sql
   INSERT INTO system_settings (`key`, value, description) 
   VALUES ('supplement_table_url', 'https://webuntis.example.com/schedule', 
           'URL für Vertretungsplan / WebUntis (Stundenplan-Änderungstabelle)');
   ```

3. **Via Dashboard**:
   Settings → Events → WebUntis / Vertretungsplan

### Creating a WebUntis Event

Once the URL is configured, events can be created through:

1. **Dashboard UI**: Select "WebUntis" as event type
2. **API**: 
   ```json
   POST /api/events
   {
     "group_id": 1,
     "title": "Daily Schedule",
     "description": "Current class schedule",
     "start": "2025-10-19T08:00:00Z",
     "end": "2025-10-19T16:00:00Z",
     "event_type": "webuntis",
     "created_by": 1
   }
   ```

No `website_url` is required—it's automatically fetched from the `supplement_table_url` system setting.

## Migration Notes

### From Presentation-Only System

This implementation extends the existing event system without breaking presentation events:

- **Presentation events**: Still use `presentation` payload with `files` array
- **Website/WebUntis events**: Use new `website` payload with `url` field
- **Message structure**: Includes `event_type` for client-side dispatch

### Future Event Types

The pattern established here can be extended to other event types:

- **Video**: `event_dict["video"] = { "type": "media", "url": "...", "autoplay": true }`
- **Message**: `event_dict["message"] = { "type": "html", "content": "..." }`
- **Custom**: Any new type with its own nested payload

## Testing Checklist

- [x] Database migration includes `webuntis` enum value
- [x] System setting `supplement_table_url` description updated to include WebUntis
- [x] Event creation validates supplement_table_url is configured
- [x] Event creation creates `EventMedia` with supplement table URL
- [x] Scheduler includes `website` payload for `webuntis` events
- [x] MQTT message structure documented
- [x] No duplicate webuntis_url setting (uses supplement_table_url)
- [ ] Dashboard UI shows supplement table URL is used for WebUntis (documentation)
- [ ] Client implementation tested with WebUntis events (client-side)

## Related Files

### Modified
- `scheduler/db_utils.py` - Event formatting logic
- `server/routes/events.py` - Event creation handling
- `server/routes/system_settings.py` - WebUntis URL endpoints
- `server/init_defaults.py` - System setting defaults

### Created
- `MQTT_EVENT_PAYLOAD_GUIDE.md` - Comprehensive message format documentation
- `WEBUNTIS_EVENT_IMPLEMENTATION.md` - This file

### Existing (Not Modified)
- `models/models.py` - Already had `webuntis` enum value
- `dashboard/src/components/CustomEventModal.tsx` - Already supports webuntis type

## Further Enhancements

### Short-term
1. Add WebUntis URL configuration to dashboard Settings page
2. Update event creation UI to explain WebUntis URL comes from settings
3. Add validation/preview for WebUntis URL in settings

### Long-term
1. Support multiple WebUntis instances (per-school in multi-tenant setup)
2. Add WebUntis-specific metadata (class filter, room filter, etc.)
3. Implement iframe sandboxing options for security
4. Add refresh intervals for dynamic WebUntis content

## Conclusion

The `webuntis` event type is now fully integrated into the infoscreen system. It uses the existing `supplement_table_url` system setting, which serves dual purposes:
1. **Vertretungsplan (supplement table)** displays in the existing settings UI
2. **WebUntis schedule** displays via the webuntis event type

This provides a clean separation between system-wide URL configuration and per-event scheduling, while maintaining backward compatibility and following established patterns for event payload structure.

The implementation demonstrates best practices:
- **Reuse existing infrastructure**: Uses supplement_table_url instead of creating duplicate settings
- **Consistency**: Follows same patterns as existing event types
- **Extensibility**: Easy to add new event types following this model
- **Documentation**: Comprehensive guides for both developers and clients
