up static routes

This commit is contained in:
Tykayn 2025-09-21 16:57:24 +02:00 committed by tykayn
parent 9cca0e4998
commit 1048f4af45
14 changed files with 2959 additions and 1076 deletions

View file

@ -0,0 +1,270 @@
/**
* OpenStreetMap OAuth2 authentication module for the OpenEventDatabase demo pages.
* This module handles authentication with OpenStreetMap and stores/retrieves auth info in localStorage.
*/
// Constants
const OSM_AUTH_STORAGE_KEY = 'oedb_osm_auth';
/**
* OSM Authentication class
*/
class OSMAuth {
constructor() {
// Initialize auth state
this.isAuthenticated = false;
this.username = '';
this.userId = '';
// Load auth info from localStorage
this.loadAuthInfo();
}
/**
* Load authentication information from localStorage
*/
loadAuthInfo() {
try {
const authInfo = localStorage.getItem(OSM_AUTH_STORAGE_KEY);
if (authInfo) {
const parsedAuthInfo = JSON.parse(authInfo);
this.isAuthenticated = true;
this.username = parsedAuthInfo.username || '';
this.userId = parsedAuthInfo.userId || '';
console.log('Loaded OSM auth info from localStorage:', this.username);
}
} catch (error) {
console.error('Error loading OSM auth info from localStorage:', error);
}
}
/**
* Save authentication information to localStorage
* @param {string} username - The OSM username
* @param {string} userId - The OSM user ID
*/
saveAuthInfo(username, userId) {
try {
const authInfo = {
username: username,
userId: userId,
timestamp: new Date().toISOString()
};
localStorage.setItem(OSM_AUTH_STORAGE_KEY, JSON.stringify(authInfo));
this.isAuthenticated = true;
this.username = username;
this.userId = userId;
console.log('Saved OSM auth info to localStorage:', username);
} catch (error) {
console.error('Error saving OSM auth info to localStorage:', error);
}
}
/**
* Clear authentication information from localStorage
*/
clearAuthInfo() {
try {
localStorage.removeItem(OSM_AUTH_STORAGE_KEY);
this.isAuthenticated = false;
this.username = '';
this.userId = '';
console.log('Cleared OSM auth info from localStorage');
} catch (error) {
console.error('Error clearing OSM auth info from localStorage:', error);
}
}
/**
* Check if the user is authenticated
* @returns {boolean} - True if the user is authenticated, false otherwise
*/
isUserAuthenticated() {
return this.isAuthenticated;
}
/**
* Get the OSM username
* @returns {string} - The OSM username
*/
getUsername() {
return this.username;
}
/**
* Get the OSM user ID
* @returns {string} - The OSM user ID
*/
getUserId() {
return this.userId;
}
/**
* Render the authentication section
* @param {string} clientId - The OAuth2 client ID
* @param {string} redirectUri - The OAuth2 redirect URI
* @param {string} scope - The OAuth2 scope
* @returns {string} - The HTML for the authentication section
*/
renderAuthSection(clientId, redirectUri, scope = 'read_prefs') {
let html = '<div class="auth-section">';
html += '<h3>OpenStreetMap Authentication</h3>';
if (this.isAuthenticated) {
html += '<div class="auth-info">';
html += '<div>';
html += `<p>Logged in as <strong>${this.username}</strong></p>`;
html += `<p><a href="https://www.openstreetmap.org/user/${this.username}" target="_blank">View OSM Profile</a></p>`;
html += `<input type="hidden" id="osmUsername" value="${this.username}">`;
html += `<input type="hidden" id="osmUserId" value="${this.userId}">`;
html += '</div>';
html += '</div>';
} else {
html += '<p>Authenticate with your OpenStreetMap account to include your username in reports.</p>';
html += `<a href="https://www.openstreetmap.org/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}" class="osm-login-btn">`;
html += '<span class="osm-logo"></span>';
html += 'Login with OpenStreetMap';
html += '</a>';
}
html += '</div>';
return html;
}
/**
* Process the OAuth2 callback
* @param {string} authCode - The authorization code from the callback
* @param {string} clientId - The OAuth2 client ID
* @param {string} clientSecret - The OAuth2 client secret
* @param {string} redirectUri - The OAuth2 redirect URI
* @returns {Promise} - A promise that resolves when the authentication is complete
*/
processAuthCallback(authCode, clientId, clientSecret, redirectUri) {
return new Promise((resolve, reject) => {
if (!authCode) {
reject(new Error('No authorization code provided'));
return;
}
console.log('Processing OAuth2 callback with auth code:', authCode);
// Exchange authorization code for access token
const tokenUrl = 'https://www.openstreetmap.org/oauth2/token';
const tokenData = {
grant_type: 'authorization_code',
code: authCode,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret
};
// Make the request
fetch(tokenUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(tokenData)
})
.then(response => {
if (!response.ok) {
throw new Error(`Token request failed with status ${response.status}`);
}
return response.json();
})
.then(tokenInfo => {
const accessToken = tokenInfo.access_token;
if (!accessToken) {
throw new Error('No access token in response');
}
// Use access token to get user information
return fetch('https://api.openstreetmap.org/api/0.6/user/details.json', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
})
.then(response => {
if (!response.ok) {
throw new Error(`User details request failed with status ${response.status}`);
}
return response.json();
})
.then(userInfo => {
const user = userInfo.user || {};
const username = user.display_name || '';
const userId = user.id || '';
if (!username) {
throw new Error('No username in user details');
}
// Save auth info to localStorage
this.saveAuthInfo(username, userId);
resolve({
username: username,
userId: userId
});
})
.catch(error => {
console.error('Error during OAuth2 authentication:', error);
reject(error);
});
});
}
}
// Create a global instance of the OSMAuth class
const osmAuth = new OSMAuth();
/**
* Initialize the authentication module
* This should be called when the page loads
*/
function initAuth() {
console.log('Initializing OSM auth module');
// Check if we have an authorization code in the URL
const urlParams = new URLSearchParams(window.location.search);
const authCode = urlParams.get('code');
if (authCode) {
console.log('Authorization code found in URL');
// Get OAuth2 configuration from the page
const clientId = document.getElementById('osmClientId')?.value || '';
const clientSecret = document.getElementById('osmClientSecret')?.value || '';
const redirectUri = document.getElementById('osmRedirectUri')?.value || '';
if (clientId && redirectUri) {
// Process the authorization code
osmAuth.processAuthCallback(authCode, clientId, clientSecret, redirectUri)
.then(userInfo => {
console.log('Authentication successful:', userInfo);
// Remove the authorization code from the URL
const url = new URL(window.location.href);
url.searchParams.delete('code');
url.searchParams.delete('state');
window.history.replaceState({}, document.title, url.toString());
// Reload the page to update the UI
window.location.reload();
})
.catch(error => {
console.error('Authentication failed:', error);
});
} else {
console.error('Missing OAuth2 configuration');
}
}
}
// Export the OSMAuth instance and helper functions
window.osmAuth = osmAuth;
window.initAuth = initAuth;
// Initialize the auth module when the page loads
document.addEventListener('DOMContentLoaded', initAuth);

View file

@ -0,0 +1,421 @@
/*
* Common CSS styles for all demo pages in the OpenEventDatabase
*/
/* General styles */
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;
}
/* Authentication section styles */
.auth-section {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
border: 1px solid #e9ecef;
}
.auth-section h3 {
margin-top: 0;
margin-bottom: 10px;
color: #333;
}
.auth-info {
display: flex;
align-items: center;
gap: 10px;
}
.auth-info img {
width: 40px;
height: 40px;
border-radius: 50%;
}
/* Improved OSM login button styling */
.osm-login-btn {
background-color: #7ebc6f;
color: white;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 15px;
border-radius: 4px;
font-weight: bold;
text-decoration: none;
transition: all 0.2s ease;
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.osm-login-btn:hover {
background-color: #6ba75e;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.osm-logo {
width: 24px;
height: 24px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm-1.66 4.322c.944 0 1.71.766 1.71 1.71s-.766 1.71-1.71 1.71-1.71-.766-1.71-1.71.766-1.71 1.71-1.71zm6.482 13.356H4.322v-1.71h12.5v1.71zm0-3.322H4.322v-1.71h12.5v1.71zm0-3.322H4.322v-1.71h12.5v1.71z"/></svg>');
background-repeat: no-repeat;
background-position: center;
}
/* Geolocation button styles */
.geolocation-btn {
background-color: #28a745;
margin-bottom: 10px;
}
.geolocation-btn:hover {
background-color: #218838;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-right: 10px;
vertical-align: middle;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Issue buttons styles */
.issue-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
.issue-button {
flex: 1;
min-width: 120px;
text-align: center;
padding: 15px 10px;
border-radius: 5px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
}
.issue-button i {
font-size: 24px;
}
.issue-button.pothole {
background-color: #ff9800;
color: white;
}
.issue-button.obstacle {
background-color: #f44336;
color: white;
}
.issue-button.vehicle {
background-color: #2196f3;
color: white;
}
.issue-button.danger {
background-color: #9c27b0;
color: white;
}
.issue-button:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}
/* View saved events link */
.view-saved-events {
display: block;
text-align: center;
margin-top: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
text-decoration: none;
color: #333;
border: 1px solid #ddd;
}
.view-saved-events:hover {
background-color: #e9ecef;
}
/* Map overlay styles */
.map-overlay {
position: absolute;
top: 10px;
left: 10px;
background: rgba(255, 255, 255, 0.9);
padding: 15px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
max-width: 300px;
max-height: 90vh;
overflow-y: auto;
}
.map-overlay h2 {
margin-top: 0;
margin-bottom: 10px;
}
/* Event list styles */
.event-list {
margin-top: 15px;
max-height: 60vh;
overflow-y: auto;
}
.event-item {
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.event-item:hover {
background-color: #f5f5f5;
}
.event-item h3 {
margin: 0 0 5px 0;
font-size: 16px;
}
.event-item p {
margin: 0;
font-size: 14px;
color: #666;
}
.event-item .event-date {
font-size: 12px;
color: #999;
}
.event-item .event-type {
display: inline-block;
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
color: white;
margin-right: 5px;
}
.event-type.pothole {
background-color: #ff9800;
}
.event-type.obstacle {
background-color: #f44336;
}
.event-type.vehicle {
background-color: #2196f3;
}
.event-type.danger {
background-color: #9c27b0;
}
.event-type.traffic {
background-color: #ff3860;
}
.event-type.other {
background-color: #777;
}
.no-events {
padding: 15px;
background-color: #f8f9fa;
border-radius: 5px;
text-align: center;
color: #666;
}
/* Controls styles */
.controls {
margin-top: 15px;
display: flex;
gap: 10px;
}
.controls button {
flex: 1;
padding: 8px;
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
.controls button:hover {
background-color: #e9ecef;
}
.controls button.danger {
color: #dc3545;
}
/* Popup styles */
.popup-content {
max-width: 300px;
}
.popup-content h3 {
margin-top: 0;
margin-bottom: 10px;
}
.popup-content table {
width: 100%;
border-collapse: collapse;
}
.popup-content table td {
padding: 4px;
border-bottom: 1px solid #eee;
}
.popup-content table td:first-child {
font-weight: bold;
width: 40%;
}
.osm-login-btn{
padding: 1rem 2rem;
border-radius: 5px;
}