449 lines
		
	
	
		
			No EOL
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			449 lines
		
	
	
		
			No EOL
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Event form resource for the OpenEventDatabase.
 | |
| """
 | |
| 
 | |
| import falcon
 | |
| from oedb.utils.logging import logger
 | |
| 
 | |
| class EventFormResource:
 | |
|     """
 | |
|     Resource for the event submission form.
 | |
|     Handles the /demo/add endpoint.
 | |
|     """
 | |
|     
 | |
|     def on_get(self, req, resp):
 | |
|         """
 | |
|         Handle GET requests to the /demo/add endpoint.
 | |
|         Returns an HTML page with a form for adding events.
 | |
|         
 | |
|         Args:
 | |
|             req: The request object.
 | |
|             resp: The response object.
 | |
|         """
 | |
|         logger.info("Processing GET request to /demo/add")
 | |
|         
 | |
|         try:
 | |
|             # Set content type to HTML
 | |
|             resp.content_type = 'text/html'
 | |
|             
 | |
|             # Create HTML response with form
 | |
|             html = """
 | |
|             <!DOCTYPE html>
 | |
|             <html lang="en">
 | |
|             <head>
 | |
|                 <meta charset="UTF-8">
 | |
|                 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | |
|                 <title>Add Event - OpenEventDatabase</title>
 | |
|                 <script src="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.js"></script>
 | |
|                 <link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
 | |
|                 <script src="https://unpkg.com/@mapbox/mapbox-gl-draw@1.4.3/dist/mapbox-gl-draw.js"></script>
 | |
|                 <link rel="stylesheet" href="https://unpkg.com/@mapbox/mapbox-gl-draw@1.4.3/dist/mapbox-gl-draw.css" type="text/css" />
 | |
|                 <script src="/static/event-types.js"></script>
 | |
|                 <style>
 | |
|                     body { 
 | |
|                         margin: 0; 
 | |
|                         padding: 20px; 
 | |
|                         font-family: Arial, sans-serif;
 | |
|                         background-color: #f5f5f5;
 | |
|                     }
 | |
|                     .container {
 | |
|                         max-width: 1000px;
 | |
|                         margin: 0 auto;
 | |
|                         background-color: white;
 | |
|                         padding: 20px;
 | |
|                         border-radius: 5px;
 | |
|                         box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
 | |
|                     }
 | |
|                     h1 { 
 | |
|                         margin-top: 0;
 | |
|                         color: #333;
 | |
|                     }
 | |
|                     .form-group {
 | |
|                         margin-bottom: 15px;
 | |
|                     }
 | |
|                     label {
 | |
|                         display: block;
 | |
|                         margin-bottom: 5px;
 | |
|                         font-weight: bold;
 | |
|                     }
 | |
|                     input[type="text"],
 | |
|                     input[type="datetime-local"],
 | |
|                     select,
 | |
|                     textarea {
 | |
|                         width: 100%;
 | |
|                         padding: 8px;
 | |
|                         border: 1px solid #ddd;
 | |
|                         border-radius: 4px;
 | |
|                         box-sizing: border-box;
 | |
|                         font-size: 14px;
 | |
|                     }
 | |
|                     .required:after {
 | |
|                         content: " *";
 | |
|                         color: red;
 | |
|                     }
 | |
|                     .form-row {
 | |
|                         display: flex;
 | |
|                         gap: 15px;
 | |
|                     }
 | |
|                     .form-row .form-group {
 | |
|                         flex: 1;
 | |
|                     }
 | |
|                     button {
 | |
|                         background-color: #0078ff;
 | |
|                         color: white;
 | |
|                         border: none;
 | |
|                         padding: 10px 15px;
 | |
|                         border-radius: 4px;
 | |
|                         cursor: pointer;
 | |
|                         font-size: 16px;
 | |
|                     }
 | |
|                     button:hover {
 | |
|                         background-color: #0056b3;
 | |
|                     }
 | |
|                     .note {
 | |
|                         font-size: 12px;
 | |
|                         color: #666;
 | |
|                         margin-top: 5px;
 | |
|                     }
 | |
|                     #map {
 | |
|                         width: 100%;
 | |
|                         height: 300px;
 | |
|                         margin-bottom: 15px;
 | |
|                         border-radius: 4px;
 | |
|                     }
 | |
|                     #result {
 | |
|                         margin-top: 20px;
 | |
|                         padding: 10px;
 | |
|                         border-radius: 4px;
 | |
|                         display: none;
 | |
|                     }
 | |
|                     #result.success {
 | |
|                         background-color: #d4edda;
 | |
|                         border: 1px solid #c3e6cb;
 | |
|                         color: #155724;
 | |
|                     }
 | |
|                     #result.error {
 | |
|                         background-color: #f8d7da;
 | |
|                         border: 1px solid #f5c6cb;
 | |
|                         color: #721c24;
 | |
|                     }
 | |
|                     .nav-links {
 | |
|                         margin-bottom: 20px;
 | |
|                     }
 | |
|                     .nav-links a {
 | |
|                         color: #0078ff;
 | |
|                         text-decoration: none;
 | |
|                         margin-right: 15px;
 | |
|                     }
 | |
|                     .nav-links a:hover {
 | |
|                         text-decoration: underline;
 | |
|                     }
 | |
|                 </style>
 | |
|             </head>
 | |
|             <body>
 | |
|                 <div class="container">
 | |
|                     <div class="nav-links">
 | |
|                         <a href="/demo">← Back to Map</a>
 | |
|                         <a href="/">API Information</a>
 | |
|                         <a href="/event">View Events</a>
 | |
|                     </div>
 | |
|                     
 | |
|                     <h1>Add New Event</h1>
 | |
|                     
 | |
|                     <form id="eventForm">
 | |
|                         <div class="form-group">
 | |
|                             <label for="label" >Event Name</label>
 | |
|                             <input type="text" id="label" name="label" >
 | |
|                         </div>
 | |
|                         
 | |
|                         <div class="form-row">
 | |
|                             <div class="form-group">
 | |
|                                 <label for="type" >Event Type</label>
 | |
|                                 <select id="type" name="type" >
 | |
|                                     <option value="scheduled">Scheduled</option>
 | |
|                                     <option value="forecast">Forecast</option>
 | |
|                                     <option value="unscheduled">Unscheduled</option>
 | |
|                                 </select>
 | |
|                             </div>
 | |
|                             
 | |
|                             <div class="form-group">
 | |
|                                 <label for="what" >What</label>
 | |
|                                 <input type="text" id="what" name="what" placeholder="e.g., traffic.accident, culture.music" >
 | |
|                                 <div class="note">
 | |
|                                     Catégorie de l'événement - Cliquez dans le champ ou utilisez le bouton pour voir les suggestions<br>
 | |
|                                     <small style="color: #0078ff;">💡 Tapez au moins 2 caractères pour filtrer les suggestions</small>
 | |
|                                 </div>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <div class="form-row">
 | |
|                             <div class="form-group">
 | |
|                                 <label for="what_series">What: Series</label>
 | |
|                                 <input type="text" id="what_series" name="what_series" placeholder="e.g., Euro 2024">
 | |
|                                 <div class="note">Series or group the event belongs to (e.g., Euro 2024, Summer Festival 2023)</div>
 | |
|                             </div>
 | |
|                             
 | |
|                             <div class="form-group">
 | |
|                                 <label for="where">Where</label>
 | |
|                                 <input type="text" id="where" name="where" placeholder="e.g., Stadium Name">
 | |
|                                 <div class="note">Specific location name (e.g., Eiffel Tower, Wembley Stadium)</div>
 | |
|                             </div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <div class="form-row">
 | |
|                             <div class="form-group">
 | |
|                                 <label for="start" >Start Time</label>
 | |
|                                 <input type="datetime-local" id="start" name="start"  value="">
 | |
|                             </div>
 | |
|                             
 | |
|                             <div class="form-group">
 | |
|                                 <label for="stop" >End Time</label>
 | |
|                                 <input type="datetime-local" id="stop" name="stop" value="">
 | |
|                             </div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <div class="form-group">
 | |
|                             <label >Location</label>
 | |
|                             <div id="map"></div>
 | |
|                             <div style="margin-top: 10px; text-align: center;">
 | |
|                                 <button type="button" id="geolocateBtn" style="background-color: #28a745; margin-bottom: 10px;">
 | |
|                                     <span id="geolocateSpinner" style="display: none;">⏳</span>
 | |
|                                     📍 Obtenir ma position actuelle
 | |
|                                 </button>
 | |
|                             </div>
 | |
|                             <div class="note">Click on the map to set the event location or use the geolocation button above</div>
 | |
|                         </div>
 | |
|                         
 | |
|                         <button type="submit">Create Event</button>
 | |
|                     </form>
 | |
|                     
 | |
|                     <div id="result"></div>
 | |
|                 </div>
 | |
|                 
 | |
|                 <script>
 | |
|                     // Set default date values (current day)
 | |
|                     function setDefaultDates() {
 | |
|                         const now = new Date();
 | |
|                         const today = now.toISOString().slice(0, 16); // Format: YYYY-MM-DDThh:mm
 | |
|                         
 | |
|                         // Set start time to current time
 | |
|                         document.getElementById('start').value = today;
 | |
|                         
 | |
|                         // Set end time to current time + 1 hour
 | |
|                         const oneHourLater = new Date(now);
 | |
|                         oneHourLater.setHours(oneHourLater.getHours() + 1);
 | |
|                         const oneHourLaterStr = oneHourLater.toISOString().slice(0, 16); // Format: YYYY-MM-DDThh:mm
 | |
|                         document.getElementById('stop').value = oneHourLaterStr;
 | |
|                     }
 | |
|                     
 | |
|                     // Call function to set default dates
 | |
|                     setDefaultDates();
 | |
|                     
 | |
|                     // Initialize the map
 | |
|                     const map = new maplibregl.Map({
 | |
|                         container: 'map',
 | |
|                         style: 'https://tiles.openfreemap.org/styles/liberty',
 | |
|                         center: [2.2137, 46.2276], // Default center (center of metropolitan France)
 | |
|                         zoom: 5
 | |
|                     });
 | |
|                     
 | |
|                     // Add navigation controls
 | |
|                     map.addControl(new maplibregl.NavigationControl());
 | |
|                     
 | |
|                     // Add marker for event location
 | |
|                     let marker = new maplibregl.Marker({
 | |
|                         draggable: true
 | |
|                     });
 | |
|                     
 | |
|                     // Set default marker at the center of metropolitan France
 | |
|                     marker.setLngLat([2.2137, 46.2276]).addTo(map);
 | |
|                     
 | |
|                     // Add marker on map click
 | |
|                     map.on('click', function(e) {
 | |
|                         marker.setLngLat(e.lngLat).addTo(map);
 | |
|                     });
 | |
| 
 | |
|                     // Handle geolocation button click
 | |
|                     document.getElementById('geolocateBtn').addEventListener('click', function() {
 | |
|                         // Show loading spinner
 | |
|                         document.getElementById('geolocateSpinner').style.display = 'inline-block';
 | |
|                         this.disabled = true;
 | |
| 
 | |
|                         // Check if geolocation is available
 | |
|                         if (navigator.geolocation) {
 | |
|                             navigator.geolocation.getCurrentPosition(
 | |
|                                 // Success callback
 | |
|                                 function(position) {
 | |
|                                     const lat = position.coords.latitude;
 | |
|                                     const lng = position.coords.longitude;
 | |
| 
 | |
|                                     // Set marker at current location
 | |
|                                     marker.setLngLat([lng, lat]).addTo(map);
 | |
| 
 | |
|                                     // Center map on current location
 | |
|                                     map.flyTo({
 | |
|                                         center: [lng, lat],
 | |
|                                         zoom: 14
 | |
|                                     });
 | |
| 
 | |
|                                     // Hide loading spinner
 | |
|                                     document.getElementById('geolocateSpinner').style.display = 'none';
 | |
|                                     document.getElementById('geolocateBtn').disabled = false;
 | |
| 
 | |
|                                     // Show success message
 | |
|                                     showResult('Position actuelle détectée avec succès', 'success');
 | |
|                                 },
 | |
|                                 // Error callback
 | |
|                                 function(error) {
 | |
|                                     // Hide loading spinner
 | |
|                                     document.getElementById('geolocateSpinner').style.display = 'none';
 | |
|                                     document.getElementById('geolocateBtn').disabled = false;
 | |
| 
 | |
|                                     // Show error message
 | |
|                                     let errorMsg = 'Impossible d\'obtenir votre position. ';
 | |
|                                     switch(error.code) {
 | |
|                                         case error.PERMISSION_DENIED:
 | |
|                                             errorMsg += 'Vous avez refusé la demande de géolocalisation.';
 | |
|                                             break;
 | |
|                                         case error.POSITION_UNAVAILABLE:
 | |
|                                             errorMsg += 'Les informations de localisation ne sont pas disponibles.';
 | |
|                                             break;
 | |
|                                         case error.TIMEOUT:
 | |
|                                             errorMsg += 'La demande de géolocalisation a expiré.';
 | |
|                                             break;
 | |
|                                         case error.UNKNOWN_ERROR:
 | |
|                                             errorMsg += 'Une erreur inconnue s\'est produite.';
 | |
|                                             break;
 | |
|                                     }
 | |
|                                     showResult(errorMsg, 'error');
 | |
|                                 },
 | |
|                                 // Options
 | |
|                                 {
 | |
|                                     enableHighAccuracy: true,
 | |
|                                     timeout: 10000,
 | |
|                                     maximumAge: 0
 | |
|                                 }
 | |
|                             );
 | |
|                         } else {
 | |
|                             // Hide loading spinner
 | |
|                             document.getElementById('geolocateSpinner').style.display = 'none';
 | |
|                             document.getElementById('geolocateBtn').disabled = false;
 | |
| 
 | |
|                             // Show error message
 | |
|                             showResult('La géolocalisation n\'est pas supportée par votre navigateur', 'error');
 | |
|                         }
 | |
|                     });
 | |
|                     
 | |
|                     // Initialize autocomplete for "what" field
 | |
|                     document.addEventListener('DOMContentLoaded', function() {
 | |
|                         const whatInput = document.getElementById('what');
 | |
|                         if (whatInput && window.initializeEventTypeAutocomplete) {
 | |
|                             initializeEventTypeAutocomplete(whatInput, function(suggestion) {
 | |
|                                 console.log('Type d\'événement sélectionné:', suggestion);
 | |
|                             });
 | |
|                             console.log('✅ Autocomplétion initialisée pour le champ "what"');
 | |
|                         }
 | |
|                     });
 | |
| 
 | |
|                     // Handle form submission
 | |
|                     document.getElementById('eventForm').addEventListener('submit', function(e) {
 | |
|                         e.preventDefault();
 | |
|                         
 | |
|                         // Get form values
 | |
|                         const label = document.getElementById('label').value;
 | |
|                         const type = document.getElementById('type').value;
 | |
|                         const what = document.getElementById('what').value;
 | |
|                         const what_series = document.getElementById('what_series').value;
 | |
|                         const where = document.getElementById('where').value;
 | |
|                         const start = document.getElementById('start').value;
 | |
|                         const stop = document.getElementById('stop').value;
 | |
|                         
 | |
|                         // Check if marker is set
 | |
|                         if (!marker.getLngLat()) {
 | |
|                             showResult('Please set a location by clicking on the map', 'error');
 | |
|                             return;
 | |
|                         }
 | |
|                         
 | |
|                         // Get marker coordinates
 | |
|                         const lngLat = marker.getLngLat();
 | |
|                         
 | |
|                         // Create event object
 | |
|                         const event = {
 | |
|                             type: 'Feature',
 | |
|                             geometry: {
 | |
|                                 type: 'Point',
 | |
|                                 coordinates: [lngLat.lng, lngLat.lat]
 | |
|                             },
 | |
|                             properties: {
 | |
|                                 label: label,
 | |
|                                 type: type,
 | |
|                                 what: what,
 | |
|                                 start: start,
 | |
|                                 stop: stop
 | |
|                             }
 | |
|                         };
 | |
|                         
 | |
|                         // Add optional properties if provided
 | |
|                         if (what_series) {
 | |
|                             event.properties['what:series'] = what_series;
 | |
|                         }
 | |
|                         
 | |
|                         if (where) {
 | |
|                             event.properties.where = where;
 | |
|                         }
 | |
|                         
 | |
|                         // Submit event to API
 | |
|                         fetch('/event', {
 | |
|                             method: 'POST',
 | |
|                             headers: {
 | |
|                                 'Content-Type': 'application/json'
 | |
|                             },
 | |
|                             body: JSON.stringify(event)
 | |
|                         })
 | |
|                         .then(response => {
 | |
|                             if (response.ok) {
 | |
|                                 return response.json();
 | |
|                             } else {
 | |
|                                 return response.text().then(text => {
 | |
|                                     throw new Error(text || response.statusText);
 | |
|                                 });
 | |
|                             }
 | |
|                         })
 | |
|                         .then(data => {
 | |
|                             showResult(`Event created successfully with ID: ${data.id}`, 'success');
 | |
|                             // Reset form
 | |
|                             document.getElementById('eventForm').reset();
 | |
|                             // Remove marker
 | |
|                             marker.remove();
 | |
|                         })
 | |
|                         .catch(error => {
 | |
|                             showResult(`Error creating event: ${error.message}`, 'error');
 | |
|                         });
 | |
|                     });
 | |
|                     
 | |
|                     // Show result message
 | |
|                     function showResult(message, type) {
 | |
|                         const resultElement = document.getElementById('result');
 | |
|                         resultElement.textContent = message;
 | |
|                         resultElement.className = type;
 | |
|                         resultElement.style.display = 'block';
 | |
|                         
 | |
|                         // Scroll to result
 | |
|                         resultElement.scrollIntoView({ behavior: 'smooth' });
 | |
|                     }
 | |
|                 </script>
 | |
|             </body>
 | |
|             </html>
 | |
|             """
 | |
|             
 | |
|             # Set the response body and status
 | |
|             resp.text = html
 | |
|             resp.status = falcon.HTTP_200
 | |
|             logger.success("Successfully processed GET request to /demo/add")
 | |
|         except Exception as e:
 | |
|             logger.error(f"Error processing GET request to /demo/add: {e}")
 | |
|             resp.status = falcon.HTTP_500
 | |
|             resp.text = f"Error: {str(e)}"
 | |
| 
 | |
| # Create a global instance of EventFormResource
 | |
| event_form = EventFormResource() | 
