(fix) Statische Startseite im Server-Modul erstellen

This commit is contained in:
2025-06-30 00:38:08 +02:00
parent c38270eb58
commit 8bacd72e87
14 changed files with 1584 additions and 5 deletions
@@ -4,6 +4,7 @@ import at.mocode.routes.artikelRoutes
import at.mocode.routes.personRoutes
import at.mocode.routes.vereinRoutes
import io.ktor.server.application.Application
import io.ktor.server.http.content.staticResources
import io.ktor.server.response.respondText
import io.ktor.server.routing.application
import io.ktor.server.routing.get
@@ -19,8 +20,11 @@ fun Application.configureRouting() {
call.respondText("OK")
}
// Root endpoint with basic information
get("/") {
// Serve static content (HTML, CSS, JS, images, etc.)
staticResources("/", "static")
// Root endpoint with basic information (API info endpoint)
get("/api") {
// Read application info from config if available
val appName = application.environment.config.propertyOrNull("application.name")?.getString() ?: "Meldestelle API Server"
val appVersion = application.environment.config.propertyOrNull("application.version")?.getString() ?: "1.0.0"
+266
View File
@@ -0,0 +1,266 @@
# Meldestelle Static Website - Maintainable Architecture
## Overview
This directory contains the static website for the Meldestelle application, designed with maintainability and extensibility in mind. The architecture separates concerns and uses a modular approach to make the codebase easy to understand, modify, and extend.
## Architecture
### Directory Structure
```
static/
├── css/ # Stylesheets (modular CSS)
│ ├── base.css # Base styles and CSS variables
│ ├── layout.css # Layout and structural styles
│ ├── components.css # Component-specific styles
│ └── responsive.css # Responsive design and media queries
├── js/ # JavaScript modules
│ ├── config-loader.js # Configuration loading and caching
│ ├── component-renderer.js # Dynamic content rendering
│ └── app.js # Main application orchestration
├── config/ # Configuration files
│ └── site-config.json # Site content and settings
├── index.html # Main HTML template
└── README.md # This documentation
```
### Design Principles
1. **Separation of Concerns**: CSS, JavaScript, and configuration are separated into logical modules
2. **Configuration-Driven**: Content is externalized to JSON configuration files
3. **Component-Based**: Reusable components with clear responsibilities
4. **Progressive Enhancement**: Works with basic HTML, enhanced with CSS and JavaScript
5. **Extensibility**: Easy to add new features, components, and content
## CSS Architecture
### CSS Custom Properties (Variables)
The `base.css` file defines CSS custom properties for consistent theming:
```css
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--success-color: #28a745;
/* ... more variables */
}
```
### Modular CSS Files
- **base.css**: Reset styles, typography, and CSS variables
- **layout.css**: Grid systems, containers, and structural layouts
- **components.css**: Individual component styles (cards, buttons, etc.)
- **responsive.css**: Media queries and responsive behavior
## JavaScript Architecture
### Module System
The JavaScript uses a modular approach with three main modules:
#### 1. ConfigLoader (`config-loader.js`)
- Loads and caches site configuration from JSON files
- Provides fallback configuration for error handling
- Singleton pattern for global access
```javascript
// Usage
const config = await window.configLoader.loadConfig();
```
#### 2. ComponentRenderer (`component-renderer.js`)
- Renders dynamic content based on configuration
- Handles component-specific logic and interactivity
- Emits custom events for extensibility
```javascript
// Usage
window.componentRenderer.init(config);
window.componentRenderer.renderAll();
```
#### 3. MeldestelleApp (`app.js`)
- Main application orchestration
- Event handling and coordination
- Error handling and user feedback
```javascript
// Usage
window.meldestelleApp.init();
```
## Configuration System
### Site Configuration (`config/site-config.json`)
The configuration file contains all site content and settings:
```json
{
"site": {
"title": "Page title",
"description": "Page description",
"logo": "🐎",
"status": { "message": "Status message", "type": "success" }
},
"features": [
{
"id": "unique-id",
"icon": "👥",
"title": "Feature Title",
"items": ["Feature item 1", "Feature item 2"]
}
],
"api": {
"title": "API Section Title",
"endpoints": [
{
"method": "GET",
"path": "/api/endpoint",
"description": "Endpoint description"
}
]
},
"footer": {
"copyright": "Copyright text",
"technology": "Technology stack"
}
}
```
## Extending the System
### Adding New Features
1. **Add to Configuration**: Update `site-config.json` with new feature data
2. **Update Renderer**: Modify `component-renderer.js` if new rendering logic is needed
3. **Add Styles**: Create new CSS rules in appropriate CSS files
4. **Handle Events**: Add event handlers in `app.js` for new interactions
### Adding New Components
1. **Create CSS**: Add component styles to `components.css`
2. **Add Renderer Method**: Create rendering method in `component-renderer.js`
3. **Update Configuration**: Add component data to configuration schema
4. **Add to HTML**: Include placeholder elements in `index.html`
### Customizing Styles
1. **Update Variables**: Modify CSS custom properties in `base.css`
2. **Override Styles**: Add specific overrides in appropriate CSS files
3. **Responsive Behavior**: Update `responsive.css` for mobile adaptations
## Event System
The application uses a custom event system for extensibility:
### Available Events
- `appInitialized`: Fired when application initialization is complete
- `featureCardClick`: Fired when a feature card is clicked
- `configChanged`: Fired when configuration is updated (for future use)
### Listening to Events
```javascript
document.addEventListener('featureCardClick', (event) => {
const { featureId, cardElement } = event.detail;
// Handle feature card click
});
```
### Emitting Custom Events
```javascript
window.meldestelleApp.emitEvent('customEvent', { data: 'value' });
```
## Error Handling
The system includes comprehensive error handling:
1. **Configuration Loading**: Fallback configuration if JSON loading fails
2. **Rendering Errors**: Graceful degradation with error messages
3. **Network Issues**: User-friendly error display with retry options
## Performance Considerations
1. **CSS Loading**: External CSS files are cached by browsers
2. **Configuration Caching**: Configuration is loaded once and cached
3. **Lazy Loading**: Components are rendered only when needed
4. **Debounced Events**: Resize events are debounced to prevent performance issues
## Browser Compatibility
- Modern browsers with ES6+ support
- CSS Grid and Flexbox support required
- Fetch API support required (or polyfill needed for older browsers)
## Development Workflow
### Making Content Changes
1. Edit `config/site-config.json`
2. Refresh the page to see changes
3. No code compilation required
### Making Style Changes
1. Edit appropriate CSS file in `css/` directory
2. Changes are immediately visible on page refresh
3. Use browser developer tools for testing
### Making Functionality Changes
1. Edit appropriate JavaScript file in `js/` directory
2. Test in browser developer console
3. Check for console errors and warnings
## Testing
The system is tested through the existing Kotlin test suite:
```bash
./gradlew test
```
Tests verify:
- Static content serving
- HTML structure integrity
- CSS and JavaScript loading
- Application initialization
## Future Enhancements
Potential areas for extension:
1. **Build System**: Add CSS/JS minification and bundling
2. **Template Engine**: Implement server-side templating
3. **Internationalization**: Add multi-language support
4. **Theme System**: Dynamic theme switching
5. **Component Library**: Expand reusable component collection
6. **API Integration**: Real-time data loading and updates
## Troubleshooting
### Common Issues
1. **Configuration Not Loading**: Check network tab for 404 errors on config file
2. **Styles Not Applied**: Verify CSS file paths and loading order
3. **JavaScript Errors**: Check browser console for error messages
4. **Content Not Rendering**: Verify configuration structure and JavaScript execution
### Debug Mode
Enable debug logging by opening browser console and checking for application logs:
```javascript
// Check if app is initialized
console.log(window.meldestelleApp.isInitialized());
// Get current configuration
console.log(window.meldestelleApp.getConfig());
```
@@ -0,0 +1,90 @@
{
"site": {
"title": "Meldestelle - Österreichisches Pferdesport Management System",
"description": "Österreichisches Pferdesport Management System",
"logo": "🐎",
"status": {
"message": "Online und betriebsbereit",
"type": "success"
}
},
"features": [
{
"id": "persons",
"icon": "👥",
"title": "Personenverwaltung",
"items": [
"Reiter und Richter verwalten",
"OEPS-Registrierungsnummern",
"Lizenzen und Qualifikationen",
"Vereinszugehörigkeiten"
]
},
{
"id": "clubs",
"icon": "🏛️",
"title": "Vereinsverwaltung",
"items": [
"Reitvereine und Clubs",
"Mitgliederverwaltung",
"Kontaktdaten und Adressen",
"Vereinsstatistiken"
]
},
{
"id": "horses",
"icon": "🐴",
"title": "Pferdeverwaltung",
"items": [
"Pferdestammdaten",
"Besitzerverhältnisse",
"Leistungsdaten",
"Gesundheitsstatus"
]
},
{
"id": "tournaments",
"icon": "🏆",
"title": "Turnierverwaltung",
"items": [
"Veranstaltungsplanung",
"Meldungen und Nennungen",
"Ergebnisse und Platzierungen",
"Meisterschaftswertungen"
]
}
],
"api": {
"title": "🔗 API Endpunkte",
"endpoints": [
{
"method": "GET",
"path": "/health",
"description": "System-Gesundheitscheck"
},
{
"method": "GET",
"path": "/api/persons",
"description": "Alle Personen abrufen"
},
{
"method": "GET",
"path": "/api/vereine",
"description": "Alle Vereine abrufen"
},
{
"method": "GET",
"path": "/api/artikel",
"description": "Alle Artikel abrufen"
}
],
"documentation": {
"text": "Vollständige API-Dokumentation verfügbar unter:",
"link": "/docs/API_Documentation.md"
}
},
"footer": {
"copyright": "© 2024 Meldestelle - Österreichisches Pferdesport Management System",
"technology": "Entwickelt mit Kotlin Multiplatform & Ktor"
}
}
@@ -0,0 +1,28 @@
/* Base styles and CSS reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
/* CSS Custom Properties for maintainability */
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
--success-color: #28a745;
--text-color: #333;
--light-bg: #f8f9fa;
--border-color: #dee2e6;
--shadow-light: 0 10px 30px rgba(0,0,0,0.2);
--border-radius: 10px;
--border-radius-large: 15px;
--transition: 0.3s ease;
}
@@ -0,0 +1,95 @@
/* Component styles */
/* Status indicator */
.status {
background: #d4edda;
color: #155724;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #c3e6cb;
}
/* Features grid */
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
margin-bottom: 40px;
}
/* Feature cards */
.feature-card {
background: var(--light-bg);
padding: 25px;
border-radius: var(--border-radius);
border-left: 4px solid var(--primary-color);
transition: transform var(--transition);
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-card h3 {
color: var(--primary-color);
margin-bottom: 15px;
font-size: 1.3rem;
}
.feature-card ul {
list-style: none;
padding-left: 0;
}
.feature-card li {
padding: 5px 0;
position: relative;
padding-left: 20px;
}
.feature-card li:before {
content: "✓";
position: absolute;
left: 0;
color: var(--success-color);
font-weight: bold;
}
/* API section */
.api-section {
background: #e9ecef;
padding: 30px;
border-radius: var(--border-radius);
margin-top: 30px;
}
.api-section h2 {
color: #495057;
margin-bottom: 20px;
}
.api-endpoints {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.endpoint {
background: white;
padding: 15px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
.endpoint-method {
font-weight: bold;
color: var(--success-color);
font-family: monospace;
}
.endpoint-path {
font-family: monospace;
color: #6c757d;
margin-left: 10px;
}
@@ -0,0 +1,39 @@
/* Layout styles */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
color: white;
margin-bottom: 40px;
padding: 40px 0;
}
.header h1 {
font-size: 3rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
}
.main-content {
background: white;
border-radius: var(--border-radius-large);
box-shadow: var(--shadow-light);
padding: 40px;
margin-bottom: 30px;
}
.footer {
text-align: center;
color: white;
opacity: 0.8;
padding: 20px 0;
}
@@ -0,0 +1,63 @@
/* Responsive styles */
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.main-content {
padding: 20px;
}
.features {
grid-template-columns: 1fr;
}
.api-endpoints {
grid-template-columns: 1fr;
}
.container {
padding: 10px;
}
.header {
padding: 20px 0;
margin-bottom: 20px;
}
.feature-card {
padding: 20px;
}
.api-section {
padding: 20px;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 1.5rem;
}
.header p {
font-size: 1rem;
}
.main-content {
padding: 15px;
margin-bottom: 15px;
}
.feature-card {
padding: 15px;
}
.api-section {
padding: 15px;
}
.endpoint {
padding: 10px;
}
}
+43 -2
View File
@@ -1,10 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meldestelle - Österreichisches Pferdesport Management System</title>
<!-- External CSS files for maintainability -->
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/layout.css">
<link rel="stylesheet" href="/css/components.css">
<link rel="stylesheet" href="/css/responsive.css">
</head>
<body>
<div class="container">
<!-- Header section - content will be populated by JavaScript -->
<header class="header">
<h1>Loading...</h1>
<p>Initializing application...</p>
</header>
<!-- Main content section -->
<main class="main-content">
<!-- Status section - content will be populated by JavaScript -->
<div class="status">
<strong>System Status:</strong> Loading...
</div>
<!-- Features section - content will be populated by JavaScript -->
<section class="features">
<!-- Feature cards will be dynamically generated -->
</section>
<!-- API section - content will be populated by JavaScript -->
<section class="api-section">
<!-- API endpoints will be dynamically generated -->
</section>
</main>
<!-- Footer section - content will be populated by JavaScript -->
<footer class="footer">
<p>Loading...</p>
</footer>
</div>
<!-- JavaScript modules for dynamic functionality -->
<script src="/js/config-loader.js"></script>
<script src="/js/component-renderer.js"></script>
<script src="/js/app.js"></script>
</body>
</html>
+194
View File
@@ -0,0 +1,194 @@
/**
* Main application script
* Coordinates configuration loading and component rendering
*/
class MeldestelleApp {
constructor() {
this.initialized = false;
this.config = null;
}
/**
* Initialize the application
*/
async init() {
if (this.initialized) {
console.warn('Application already initialized');
return;
}
try {
console.log('Initializing Meldestelle application...');
// Load configuration
this.config = await window.configLoader.loadConfig();
console.log('Configuration loaded:', this.config);
// Initialize component renderer
window.componentRenderer.init(this.config);
// Render all components
window.componentRenderer.renderAll();
// Add interactivity
window.componentRenderer.addInteractivity();
// Set up event listeners
this.setupEventListeners();
this.initialized = true;
console.log('Meldestelle application initialized successfully');
// Emit initialization complete event
this.emitEvent('appInitialized', { config: this.config });
} catch (error) {
console.error('Failed to initialize application:', error);
this.handleInitializationError(error);
}
}
/**
* Set up global event listeners
*/
setupEventListeners() {
// Listen for feature card clicks
document.addEventListener('featureCardClick', (event) => {
this.handleFeatureCardClick(event.detail);
});
// Listen for configuration changes (for future extensibility)
document.addEventListener('configChanged', (event) => {
this.handleConfigChange(event.detail);
});
// Handle window resize for responsive behavior
window.addEventListener('resize', this.debounce(() => {
this.handleResize();
}, 250));
}
/**
* Handle feature card clicks
* @param {Object} detail - Event detail with featureId and cardElement
*/
handleFeatureCardClick(detail) {
const { featureId, cardElement } = detail;
// Example extensible behavior - could be configured
switch (featureId) {
case 'persons':
console.log('Persons management clicked - could navigate to /api/persons');
break;
case 'clubs':
console.log('Clubs management clicked - could navigate to /api/vereine');
break;
case 'horses':
console.log('Horses management clicked - could navigate to horses section');
break;
case 'tournaments':
console.log('Tournaments management clicked - could navigate to tournaments section');
break;
default:
console.log(`Unknown feature clicked: ${featureId}`);
}
}
/**
* Handle configuration changes (for future extensibility)
* @param {Object} newConfig - New configuration
*/
handleConfigChange(newConfig) {
console.log('Configuration changed, re-rendering...');
this.config = newConfig;
window.componentRenderer.init(newConfig);
window.componentRenderer.renderAll();
window.componentRenderer.addInteractivity();
}
/**
* Handle window resize
*/
handleResize() {
// Could implement responsive behavior adjustments here
console.log('Window resized');
}
/**
* Handle initialization errors
* @param {Error} error - Initialization error
*/
handleInitializationError(error) {
// Show user-friendly error message
const container = document.querySelector('.container');
if (container) {
container.innerHTML = `
<div style="text-align: center; color: white; padding: 40px;">
<h1>⚠️ Fehler beim Laden</h1>
<p>Die Anwendung konnte nicht geladen werden.</p>
<p style="font-size: 0.9em; opacity: 0.8;">Bitte versuchen Sie es später erneut oder kontaktieren Sie den Administrator.</p>
<button onclick="location.reload()" style="margin-top: 20px; padding: 10px 20px; background: white; border: none; border-radius: 5px; cursor: pointer;">
Seite neu laden
</button>
</div>
`;
}
}
/**
* Emit custom events
* @param {string} eventName - Event name
* @param {Object} detail - Event detail
*/
emitEvent(eventName, detail = {}) {
const event = new CustomEvent(eventName, { detail });
document.dispatchEvent(event);
}
/**
* Debounce utility function
* @param {Function} func - Function to debounce
* @param {number} wait - Wait time in milliseconds
* @returns {Function} Debounced function
*/
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
/**
* Get current configuration
* @returns {Object|null} Current configuration
*/
getConfig() {
return this.config;
}
/**
* Check if application is initialized
* @returns {boolean} Initialization status
*/
isInitialized() {
return this.initialized;
}
}
// Create global app instance
window.meldestelleApp = new MeldestelleApp();
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.meldestelleApp.init();
});
} else {
// DOM is already ready
window.meldestelleApp.init();
}
@@ -0,0 +1,162 @@
/**
* Component renderer module
* Handles dynamic rendering of page components based on configuration
*/
class ComponentRenderer {
constructor() {
this.config = null;
}
/**
* Initialize renderer with configuration
* @param {Object} config - Site configuration
*/
init(config) {
this.config = config;
}
/**
* Render the header section
*/
renderHeader() {
const header = document.querySelector('.header');
if (!header || !this.config.site) return;
header.innerHTML = `
<h1>${this.config.site.logo} ${this.config.site.title.split(' - ')[0]}</h1>
<p>${this.config.site.description}</p>
`;
}
/**
* Render the status section
*/
renderStatus() {
const statusElement = document.querySelector('.status');
if (!statusElement || !this.config.site.status) return;
statusElement.innerHTML = `
<strong>System Status:</strong> ${this.config.site.status.message}
`;
}
/**
* Render feature cards
*/
renderFeatures() {
const featuresContainer = document.querySelector('.features');
if (!featuresContainer || !this.config.features) return;
featuresContainer.innerHTML = this.config.features.map(feature => `
<div class="feature-card" data-feature-id="${feature.id}">
<h3>${feature.icon} ${feature.title}</h3>
<ul>
${feature.items.map(item => `<li>${item}</li>`).join('')}
</ul>
</div>
`).join('');
}
/**
* Render API section
*/
renderApiSection() {
const apiSection = document.querySelector('.api-section');
if (!apiSection || !this.config.api) return;
const endpointsHtml = this.config.api.endpoints.map(endpoint => `
<div class="endpoint">
<span class="endpoint-method">${endpoint.method}</span>
<span class="endpoint-path">${endpoint.path}</span>
<div>${endpoint.description}</div>
</div>
`).join('');
apiSection.innerHTML = `
<h2>${this.config.api.title}</h2>
<div class="api-endpoints">
${endpointsHtml}
</div>
<p style="margin-top: 20px; font-style: italic;">
${this.config.api.documentation.text}
<a href="${this.config.api.documentation.link}" style="color: var(--primary-color);">${this.config.api.documentation.link}</a>
</p>
`;
}
/**
* Render footer section
*/
renderFooter() {
const footer = document.querySelector('.footer');
if (!footer || !this.config.footer) return;
footer.innerHTML = `
<p>${this.config.footer.copyright}</p>
<p>${this.config.footer.technology}</p>
`;
}
/**
* Update page title
*/
updatePageTitle() {
if (this.config.site && this.config.site.title) {
document.title = this.config.site.title;
}
}
/**
* Render all components
*/
renderAll() {
if (!this.config) {
console.warn('No configuration available for rendering');
return;
}
this.updatePageTitle();
this.renderHeader();
this.renderStatus();
this.renderFeatures();
this.renderApiSection();
this.renderFooter();
}
/**
* Add feature card click handlers for extensibility
*/
addInteractivity() {
const featureCards = document.querySelectorAll('.feature-card');
featureCards.forEach(card => {
card.addEventListener('click', (e) => {
const featureId = card.getAttribute('data-feature-id');
this.onFeatureCardClick(featureId, card);
});
});
}
/**
* Handle feature card clicks (extensible)
* @param {string} featureId - Feature identifier
* @param {HTMLElement} cardElement - Card element
*/
onFeatureCardClick(featureId, cardElement) {
// Add visual feedback
cardElement.style.transform = 'scale(0.98)';
setTimeout(() => {
cardElement.style.transform = '';
}, 150);
// Emit custom event for extensibility
const event = new CustomEvent('featureCardClick', {
detail: { featureId, cardElement }
});
document.dispatchEvent(event);
console.log(`Feature card clicked: ${featureId}`);
}
}
// Export singleton instance
window.componentRenderer = new ComponentRenderer();
@@ -0,0 +1,78 @@
/**
* Configuration loader module
* Handles loading and caching of site configuration
*/
class ConfigLoader {
constructor() {
this.config = null;
this.loaded = false;
}
/**
* Load configuration from JSON file
* @returns {Promise<Object>} Site configuration
*/
async loadConfig() {
if (this.loaded && this.config) {
return this.config;
}
try {
const response = await fetch('/config/site-config.json');
if (!response.ok) {
throw new Error(`Failed to load config: ${response.status}`);
}
this.config = await response.json();
this.loaded = true;
return this.config;
} catch (error) {
console.error('Error loading configuration:', error);
// Return fallback configuration
return this.getFallbackConfig();
}
}
/**
* Get fallback configuration if loading fails
* @returns {Object} Fallback configuration
*/
getFallbackConfig() {
return {
site: {
title: "Meldestelle - Österreichisches Pferdesport Management System",
description: "Österreichisches Pferdesport Management System",
logo: "🐎",
status: {
message: "Online und betriebsbereit",
type: "success"
}
},
features: [],
api: {
title: "🔗 API Endpunkte",
endpoints: [],
documentation: {
text: "API-Dokumentation",
link: "/docs/API_Documentation.md"
}
},
footer: {
copyright: "© 2024 Meldestelle",
technology: "Entwickelt mit Kotlin Multiplatform & Ktor"
}
};
}
/**
* Get specific configuration section
* @param {string} section - Configuration section name
* @returns {*} Configuration section or null
*/
getSection(section) {
return this.config ? this.config[section] : null;
}
}
// Export singleton instance
window.configLoader = new ConfigLoader();
@@ -63,7 +63,22 @@ class ApplicationTest {
client.get("/").apply {
assertEquals(HttpStatusCode.OK, status)
val responseText = bodyAsText()
// The response format is: "Meldestelle API Server v1.0.0 - Running in development mode"
// The root endpoint now serves the static HTML start page
assertTrue(responseText.contains("<!DOCTYPE html>"), "Response should contain HTML doctype, but was: ${responseText.take(100)}...")
assertTrue(responseText.contains("Meldestelle"), "Response should contain 'Meldestelle', but was: ${responseText.take(100)}...")
assertTrue(responseText.contains("Österreichisches Pferdesport Management System"), "Response should contain system description")
}
}
@Test
fun testApiInfoEndpoint() = testApplication {
application {
module()
}
client.get("/api").apply {
assertEquals(HttpStatusCode.OK, status)
val responseText = bodyAsText()
// The API info endpoint format is: "Meldestelle API Server v1.0.0 - Running in development mode"
assertTrue(responseText.contains("Meldestelle API Server"), "Response should contain 'Meldestelle API Server', but was: $responseText")
assertTrue(responseText.contains("v1.0.0"), "Response should contain 'v1.0.0', but was: $responseText")
assertTrue(responseText.contains("development"), "Response should contain 'development', but was: $responseText")