feat: Add database migrations and auth system

- Add Alembic for database migrations
- Implement user authentication system
- Update frontend styles and components
- Add new test audio functionality
- Update stream management and UI
This commit is contained in:
oib
2025-07-02 09:37:03 +02:00
parent 39934115a1
commit 17616ac5b8
49 changed files with 5059 additions and 804 deletions

View File

@ -8,44 +8,250 @@ function getCookie(name) {
}
// dashboard.js — toggle guest vs. user dashboard and reposition streams link
// Logout function
let isLoggingOut = false;
async function handleLogout(event) {
// Prevent multiple simultaneous logout attempts
if (isLoggingOut) return;
isLoggingOut = true;
// Prevent default button behavior
if (event) {
event.preventDefault();
event.stopPropagation();
}
try {
console.log('[LOGOUT] Starting logout process');
// Clear user data from localStorage
localStorage.removeItem('uid');
localStorage.removeItem('uid_time');
localStorage.removeItem('confirmed_uid');
localStorage.removeItem('last_page');
// Clear cookie
document.cookie = 'uid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
// Update UI state immediately
const userDashboard = document.getElementById('user-dashboard');
const guestDashboard = document.getElementById('guest-dashboard');
const logoutButton = document.getElementById('logout-button');
const deleteAccountButton = document.getElementById('delete-account-button');
if (userDashboard) userDashboard.style.display = 'none';
if (guestDashboard) guestDashboard.style.display = 'block';
if (logoutButton) logoutButton.style.display = 'none';
if (deleteAccountButton) deleteAccountButton.style.display = 'none';
// Show success message (only once)
if (window.showToast) {
showToast('Logged out successfully');
} else {
console.log('Logged out successfully');
}
// Navigate to register page
if (window.showOnly) {
window.showOnly('register-page');
} else {
// Fallback to URL change if showOnly isn't available
window.location.href = '/#register-page';
}
console.log('[LOGOUT] Logout completed');
} catch (error) {
console.error('[LOGOUT] Logout failed:', error);
if (window.showToast) {
showToast('Logout failed. Please try again.');
}
} finally {
isLoggingOut = false;
}
}
// Delete account function
async function handleDeleteAccount() {
try {
const uid = localStorage.getItem('uid');
if (!uid) {
showToast('No user session found. Please log in again.');
return;
}
// Show confirmation dialog
const confirmed = confirm('⚠️ WARNING: This will permanently delete your account and all your data. This action cannot be undone.\n\nAre you sure you want to delete your account?');
if (!confirmed) {
return; // User cancelled the deletion
}
// Show loading state
const deleteButton = document.getElementById('delete-account-button');
const originalText = deleteButton.textContent;
deleteButton.disabled = true;
deleteButton.textContent = 'Deleting...';
// Call the delete account endpoint
const response = await fetch(`/api/delete-account`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ uid }),
});
const result = await response.json();
if (response.ok) {
showToast('Account deleted successfully');
// Clear user data
localStorage.removeItem('uid');
localStorage.removeItem('uid_time');
localStorage.removeItem('confirmed_uid');
document.cookie = 'uid=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
// Redirect to home page
setTimeout(() => {
window.location.href = '/';
}, 1000);
} else {
throw new Error(result.detail || 'Failed to delete account');
}
} catch (error) {
console.error('Delete account failed:', error);
showToast(`Failed to delete account: ${error.message}`);
// Reset button state
const deleteButton = document.getElementById('delete-account-button');
if (deleteButton) {
deleteButton.disabled = false;
deleteButton.textContent = '🗑️ Delete Account';
}
}
}
async function initDashboard() {
// New dashboard toggling logic
console.log('[DASHBOARD] Initializing dashboard...');
// Get all dashboard elements
const guestDashboard = document.getElementById('guest-dashboard');
const userDashboard = document.getElementById('user-dashboard');
const userUpload = document.getElementById('user-upload-area');
// Hide all by default
if (guestDashboard) guestDashboard.style.display = 'none';
if (userDashboard) userDashboard.style.display = 'none';
if (userUpload) userUpload.style.display = 'none';
const logoutButton = document.getElementById('logout-button');
const deleteAccountButton = document.getElementById('delete-account-button');
console.log('[DASHBOARD] Elements found:', {
guestDashboard: !!guestDashboard,
userDashboard: !!userDashboard,
userUpload: !!userUpload,
logoutButton: !!logoutButton,
deleteAccountButton: !!deleteAccountButton
});
// Add click event listeners for logout and delete account buttons
if (logoutButton) {
console.log('[DASHBOARD] Adding logout button handler');
logoutButton.addEventListener('click', handleLogout);
}
if (deleteAccountButton) {
console.log('[DASHBOARD] Adding delete account button handler');
deleteAccountButton.addEventListener('click', (e) => {
e.preventDefault();
handleDeleteAccount();
});
}
const uid = getCookie('uid');
console.log('[DASHBOARD] UID from cookie:', uid);
// Guest view
if (!uid) {
// Guest view: only nav
if (guestDashboard) guestDashboard.style.display = '';
if (userDashboard) userDashboard.style.display = 'none';
if (userUpload) userUpload.style.display = 'none';
console.log('[DASHBOARD] No UID found, showing guest dashboard');
if (guestDashboard) guestDashboard.style.display = 'block';
if (userDashboard) userDashboard.style.display = 'none';
if (userUpload) userUpload.style.display = 'none';
if (logoutButton) logoutButton.style.display = 'none';
if (deleteAccountButton) deleteAccountButton.style.display = 'none';
const mePage = document.getElementById('me-page');
if (mePage) mePage.style.display = 'none';
return;
}
// Logged-in view - show user dashboard by default
console.log('[DASHBOARD] User is logged in, showing user dashboard');
// Log current display states
console.log('[DASHBOARD] Current display states:', {
guestDashboard: guestDashboard ? window.getComputedStyle(guestDashboard).display : 'not found',
userDashboard: userDashboard ? window.getComputedStyle(userDashboard).display : 'not found',
userUpload: userUpload ? window.getComputedStyle(userUpload).display : 'not found',
logoutButton: logoutButton ? window.getComputedStyle(logoutButton).display : 'not found',
deleteAccountButton: deleteAccountButton ? window.getComputedStyle(deleteAccountButton).display : 'not found'
});
// Show delete account button for logged-in users
if (deleteAccountButton) {
deleteAccountButton.style.display = 'block';
console.log('[DASHBOARD] Showing delete account button');
}
// Hide guest dashboard
if (guestDashboard) {
console.log('[DASHBOARD] Hiding guest dashboard');
guestDashboard.style.display = 'none';
}
// Show user dashboard
if (userDashboard) {
console.log('[DASHBOARD] Showing user dashboard');
userDashboard.style.display = 'block';
userDashboard.style.visibility = 'visible';
userDashboard.hidden = false;
// Debug: Check if the element is actually in the DOM
console.log('[DASHBOARD] User dashboard parent:', userDashboard.parentElement);
console.log('[DASHBOARD] User dashboard computed display:', window.getComputedStyle(userDashboard).display);
} else {
console.error('[DASHBOARD] userDashboard element not found!');
}
// Show essential elements for logged-in users
const linksSection = document.getElementById('links');
if (linksSection) {
console.log('[DASHBOARD] Showing links section');
linksSection.style.display = 'block';
}
const showMeLink = document.getElementById('show-me');
if (showMeLink && showMeLink.parentElement) {
console.log('[DASHBOARD] Showing show-me link');
showMeLink.parentElement.style.display = 'block';
}
// Show me-page for logged-in users
const mePage = document.getElementById('me-page');
if (mePage) mePage.style.display = 'none';
return;
}
if (mePage) {
console.log('[DASHBOARD] Showing me-page');
mePage.style.display = 'block';
}
try {
console.log(`[DEBUG] Fetching user data for UID: ${uid}`);
const res = await fetch(`/me/${uid}`);
if (!res.ok) throw new Error('Not authorized');
if (!res.ok) {
const errorText = await res.text();
console.error(`[ERROR] Failed to fetch user data: ${res.status} ${res.statusText}`, errorText);
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
// Logged-in view
// Restore links section and show-me link
const linksSection = document.getElementById('links');
if (linksSection) linksSection.style.display = '';
const showMeLink = document.getElementById('show-me');
if (showMeLink && showMeLink.parentElement) showMeLink.parentElement.style.display = '';
// Show me-page for logged-in users
const mePage = document.getElementById('me-page');
if (mePage) mePage.style.display = '';
console.log('[DEBUG] User data loaded:', data);
// Ensure upload area is visible if last_page was me-page
const userUpload = document.getElementById('user-upload-area');
if (userUpload && localStorage.getItem('last_page') === 'me-page') {
// userUpload visibility is now only controlled by nav.js SPA logic
}
@ -53,19 +259,40 @@ async function initDashboard() {
// Remove guest warning if present
const guestMsg = document.getElementById('guest-warning-msg');
if (guestMsg && guestMsg.parentNode) guestMsg.parentNode.removeChild(guestMsg);
userDashboard.style.display = '';
// Show user dashboard and logout button
if (userDashboard) userDashboard.style.display = '';
if (logoutButton) {
logoutButton.style.display = 'block';
logoutButton.onclick = handleLogout;
}
// Set audio source
const meAudio = document.getElementById('me-audio');
if (meAudio && uid) {
meAudio.src = `/audio/${encodeURIComponent(uid)}/stream.opus`;
if (meAudio && data && data.username) {
// Use username instead of UID for the audio file path
meAudio.src = `/audio/${encodeURIComponent(data.username)}/stream.opus?t=${Date.now()}`;
console.log('Setting audio source to:', meAudio.src);
} else if (meAudio && uid) {
// Fallback to UID if username is not available
meAudio.src = `/audio/${encodeURIComponent(uid)}/stream.opus?t=${Date.now()}`;
console.warn('Using UID fallback for audio source:', meAudio.src);
}
// Update quota
// Update quota and ensure quota meter is visible
const quotaMeter = document.getElementById('quota-meter');
const quotaBar = document.getElementById('quota-bar');
const quotaText = document.getElementById('quota-text');
if (quotaBar) quotaBar.value = data.quota;
if (quotaText) quotaText.textContent = `${data.quota} MB used`;
if (quotaMeter) {
quotaMeter.hidden = false;
quotaMeter.style.display = 'block'; // Ensure it's not hidden by display:none
}
// Fetch and display the list of uploaded files if the function is available
if (window.fetchAndDisplayFiles) {
window.fetchAndDisplayFiles(uid);
}
// Ensure Streams link remains in nav, not moved
// (No action needed if static)