add plan in todo readme

This commit is contained in:
Tykayn 2025-09-16 01:09:20 +02:00 committed by tykayn
parent afe83a9a3c
commit 2c4f79edbb
4 changed files with 450 additions and 4 deletions

View file

@ -155,6 +155,9 @@ page de démo listant les évènements selon leur type, les afficher sur une car
créer une page de démo qui permet de modifier un évènement, faire un lien vers cette page quand on ouvre une popup d'évènement sur la page de /demo. et afficher une icone pour les marqueurs de carte selon le type d'évènement, définis en quelques uns et utilise les icones de bulma css.
vérifier le fonctionnement des endpoints de recherche avec les queryparameters, les mettre dans la page de démo.
la page /demo/by-what a une erreur, Error: Expecting value: line 1 column 1 (char 0)
récupérer les évènements depuis osmcal dans esm_cal.py
dans les extracteurs, vérifier qu'il n'existe pas déjà des évènements avec les mês propriétés avant de les créer.
## License

View file

@ -61,6 +61,7 @@ def create_app():
app.add_route('/demo/add', event_form) # Handle event submission form
app.add_route('/demo/by-what', demo, suffix='by_what') # Handle events by type page
app.add_route('/demo/map-by-what', demo, suffix='map_by_what') # Handle map by event type page
app.add_route('/demo/edit/{id}', demo, suffix='edit') # Handle event editing page
logger.success("Application initialized successfully")
return app

2
extractors/osm_cal.py Normal file
View file

@ -0,0 +1,2 @@
# récupérer les évènements depuis osmcal.depuis
# https://osmcal.org/events.rss

View file

@ -11,9 +11,403 @@ from oedb.utils.logging import logger
class DemoResource:
"""
Resource for the demo endpoint.
Handles the /demo endpoint.
Handles the /demo endpoint and related demo pages.
"""
def on_get_edit(self, req, resp, id=None):
"""
Handle GET requests to the /demo/edit endpoint.
Returns an HTML page with a form for editing an existing event.
Args:
req: The request object.
resp: The response object.
id: The event ID to edit.
"""
logger.info(f"Processing GET request to /demo/edit for event ID: {id}")
if id is None:
resp.status = falcon.HTTP_400
resp.text = "Event ID is required"
return
try:
# Set content type to HTML
resp.content_type = 'text/html'
# Fetch the event data from the API
response = requests.get(f'http://localhost/event/{id}')
if response.status_code != 200:
resp.status = falcon.HTTP_404
resp.text = f"Event with ID {id} not found"
return
event_data = response.json()
# Create HTML response with form
html = f"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Edit 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" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<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">&larr; Back to Map</a>
<a href="/">API Information</a>
<a href="/event">View Events</a>
</div>
<h1>Edit Event</h1>
<form id="eventForm">
<input type="hidden" id="eventId" value="{id}">
<div class="form-group">
<label for="label" class="required">Event Name</label>
<input type="text" id="label" name="label" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="type" class="required">Event Type</label>
<select id="type" name="type" required>
<option value="scheduled">Scheduled</option>
<option value="forecast">Forecast</option>
<option value="unscheduled">Unscheduled</option>
</select>
</div>
<div class="form-group">
<label for="what" class="required">What</label>
<input type="text" id="what" name="what" placeholder="e.g., sport.match.football" required>
<div class="note">Category of the event (e.g., sport.match.football, culture.festival)</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" class="required">Start Time</label>
<input type="datetime-local" id="start" name="start" required value="">
</div>
<div class="form-group">
<label for="stop" class="required">End Time</label>
<input type="datetime-local" id="stop" name="stop" required value="">
</div>
</div>
<div class="form-group">
<label class="required">Location</label>
<div id="map"></div>
<div class="note">Click on the map to set the event location</div>
</div>
<button type="submit">Update Event</button>
</form>
<div id="result"></div>
</div>
<script>
// Initialize the map
const map = new maplibregl.Map({{
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
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
}});
// Event data from API
const eventData = {event_data};
// Function to populate form with event data
function populateForm() {{
if (!eventData || !eventData.properties) {{
showResult('Error loading event data', 'error');
return;
}}
const properties = eventData.properties;
// Set form values
document.getElementById('label').value = properties.label || '';
document.getElementById('type').value = properties.type || 'scheduled';
document.getElementById('what').value = properties.what || '';
// Handle optional fields
if (properties['what:series']) {{
document.getElementById('what_series').value = properties['what:series'];
}}
if (properties.where) {{
document.getElementById('where').value = properties.where;
}}
// Format dates for datetime-local input
if (properties.start) {{
const startDate = new Date(properties.start);
document.getElementById('start').value = startDate.toISOString().slice(0, 16);
}}
if (properties.stop) {{
const stopDate = new Date(properties.stop);
document.getElementById('stop').value = stopDate.toISOString().slice(0, 16);
}}
// Set marker on map
if (eventData.geometry && eventData.geometry.coordinates) {{
const coords = eventData.geometry.coordinates;
marker.setLngLat(coords).addTo(map);
// Center map on event location
map.flyTo({{
center: coords,
zoom: 10
}});
}}
}}
// Call function to populate form
populateForm();
// Add marker on map click
map.on('click', function(e) {{
marker.setLngLat(e.lngLat).addTo(map);
}});
// Function to 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' }});
}}
// Handle form submission
document.getElementById('eventForm').addEventListener('submit', function(e) {{
e.preventDefault();
// Get event ID
const eventId = document.getElementById('eventId').value;
// 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/${{eventId}}`, {{
method: 'PUT',
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 updated successfully with ID: ${{data.id}}`, 'success');
// Add link to view the event
const resultElement = document.getElementById('result');
resultElement.innerHTML += `<p><a href="/event/${{data.id}}" target="_blank">View Event</a> | <a href="/demo">Back to Map</a></p>`;
}})
.catch(error => {{
showResult(`Error: ${{error.message}}`, 'error');
}});
}});
</script>
</body>
</html>
"""
# Set the response body and status
resp.text = html.replace('{event_data}', json.dumps(event_data))
resp.status = falcon.HTTP_200
logger.success(f"Successfully processed GET request to /demo/edit for event ID: {id}")
except Exception as e:
logger.error(f"Error processing GET request to /demo/edit: {e}")
resp.status = falcon.HTTP_500
resp.text = f"Error: {str(e)}"
def on_get(self, req, resp):
"""
Handle GET requests to the /demo endpoint.
@ -39,6 +433,8 @@ class DemoResource:
<title>OpenEventDatabase Demo</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" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
<style>
body { margin: 0; padding: 0; font-family: Arial, sans-serif; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
@ -182,6 +578,11 @@ class DemoResource:
popupContent += '</table>';
popupContent += '</div>';
// Add edit link
popupContent += `<div style="margin-top: 10px; text-align: center;">
<a href="/demo/edit/${properties.id}" class="edit-event-btn" style="display: inline-block; padding: 5px 10px; background-color: #0078ff; color: white; border-radius: 4px; text-decoration: none; font-weight: bold;">Edit Event</a>
</div>`;
popupContent += '</div>';
// Create popup
@ -190,10 +591,49 @@ class DemoResource:
closeOnClick: true
}).setHTML(popupContent);
// Get event type for icon selection
const eventType = properties.what || 'unknown';
// Define icon based on event type
let iconClass = 'info-circle'; // Default icon
let iconColor = '#0078ff'; // Default color
// Map event types to icons
if (eventType.startsWith('weather')) {
iconClass = 'cloud';
iconColor = '#00d1b2'; // Teal
} else if (eventType.startsWith('traffic')) {
iconClass = 'car';
iconColor = '#ff3860'; // Red
} else if (eventType.startsWith('sport')) {
iconClass = 'futbol';
iconColor = '#3273dc'; // Blue
} else if (eventType.startsWith('culture')) {
iconClass = 'theater-masks';
iconColor = '#ffdd57'; // Yellow
} else if (eventType.startsWith('health')) {
iconClass = 'heartbeat';
iconColor = '#ff3860'; // Red
} else if (eventType.startsWith('education')) {
iconClass = 'graduation-cap';
iconColor = '#3273dc'; // Blue
} else if (eventType.startsWith('politics')) {
iconClass = 'landmark';
iconColor = '#209cee'; // Light blue
} else if (eventType.startsWith('nature')) {
iconClass = 'leaf';
iconColor = '#23d160'; // Green
}
// Create custom HTML element for marker
const el = document.createElement('div');
el.className = 'marker';
el.innerHTML = `<span class="icon" style="background-color: white; border-radius: 50%; padding: 8px; box-shadow: 0 0 5px rgba(0,0,0,0.3);">
<i class="fas fa-${iconClass}" style="color: ${iconColor}; font-size: 16px;"></i>
</span>`;
// Add marker with popup
new maplibregl.Marker({
color: '#FF5722'
})
new maplibregl.Marker(el)
.setLngLat(coordinates)
.setPopup(popup)
.addTo(map);