OPML logic

This commit is contained in:
Greg Shuflin 2025-02-15 04:59:09 -08:00
parent 2041e39ab8
commit 98ff8be9d8
3 changed files with 137 additions and 46 deletions

52
src/import.rs Normal file
View File

@ -0,0 +1,52 @@
// Module for handling OPML feed list imports
use std::io::Cursor;
use rocket::data::ToByteUnit;
use rocket::http::Status;
use rocket::serde::json::Json;
use rocket::serde::{Deserialize, Serialize};
use rocket::{post, Data};
use rocket_db_pools::Connection;
use tracing::error;
use crate::user::AuthenticatedUser;
use crate::Db;
#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct ImportResponse {
success: bool,
message: String,
feeds_imported: Option<usize>,
}
/// Import feeds from an OPML file
#[post("/import/opml", data = "<file>")]
pub async fn import_opml(
mut _db: Connection<Db>,
_user: AuthenticatedUser,
file: Data<'_>,
) -> Result<Json<ImportResponse>, Status> {
// Limit file size to 1MB
let file_data = file.open(1.mebibytes()).into_bytes().await.map_err(|e| {
error!("Failed to read OPML file: {}", e);
Status::BadRequest
})?;
if !file_data.is_complete() {
error!("OPML file too large");
return Err(Status::PayloadTooLarge);
}
let bytes = file_data.value;
let _cursor = Cursor::new(bytes);
// TODO: Parse OPML and import feeds
// For now just return a placeholder response
Ok(Json(ImportResponse {
success: true,
message: "OPML file received successfully. Import not yet implemented.".to_string(),
feeds_imported: None,
}))
}

View File

@ -8,6 +8,7 @@ use tracing_subscriber::FmtSubscriber;
mod demo; mod demo;
mod feed_utils; mod feed_utils;
mod feeds; mod feeds;
mod import;
mod poll; mod poll;
mod poll_utils; mod poll_utils;
mod session_store; mod session_store;
@ -162,6 +163,7 @@ fn rocket() -> _ {
feeds::delete_feed, feeds::delete_feed,
poll::poll_feed, poll::poll_feed,
poll::update_entry_state, poll::update_entry_state,
import::import_opml,
], ],
) )
.mount("/static", FileServer::from("static")) .mount("/static", FileServer::from("static"))

View File

@ -1,20 +1,23 @@
// Add at the beginning of the file
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Mobile menu functionality // Mobile menu functionality
const hamburgerMenu = document.getElementById('hamburgerMenu'); const hamburgerMenu = document.getElementById('hamburgerMenu');
const sidebarClose = document.getElementById('sidebarClose'); const sidebarClose = document.getElementById('sidebarClose');
const sidebar = document.getElementById('sidebar'); const sidebar = document.getElementById('sidebar');
const userMenuButton = document.getElementById('userMenuButton');
const userMenuDropdown = document.getElementById('userMenuDropdown');
// Add OPML import button handler // Create a hidden file input for OPML upload
const importOpmlButton = document.getElementById('importOpmlButton'); const opmlFileInput = document.createElement('input');
importOpmlButton.addEventListener('click', function() { opmlFileInput.type = 'file';
console.log('OPML import button clicked - functionality coming soon!'); opmlFileInput.accept = '.opml,.xml';
}); opmlFileInput.style.display = 'none';
document.body.appendChild(opmlFileInput);
function toggleSidebar() { function toggleSidebar() {
sidebar.classList.toggle('active'); sidebar.classList.toggle('active');
} }
// Mobile menu handlers
hamburgerMenu.addEventListener('click', toggleSidebar); hamburgerMenu.addEventListener('click', toggleSidebar);
sidebarClose.addEventListener('click', toggleSidebar); sidebarClose.addEventListener('click', toggleSidebar);
@ -28,6 +31,80 @@ document.addEventListener('DOMContentLoaded', function() {
sidebar.classList.remove('active'); sidebar.classList.remove('active');
} }
}); });
// OPML import handlers
opmlFileInput.addEventListener('change', async (e) => {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/import/opml', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
if (result.success) {
alert('OPML import successful: ' + result.message);
// Refresh the feed list if feeds were imported
if (result.feeds_imported) {
handleFeeds();
}
} else {
alert('OPML import failed: ' + result.message);
}
} else {
alert('Failed to import OPML file. Please try again.');
}
} catch (error) {
console.error('OPML import failed:', error);
alert('Failed to import OPML file. Please try again.');
}
}
// Clear the input so the same file can be selected again
e.target.value = '';
});
// User menu handlers
userMenuButton.addEventListener('click', (e) => {
e.stopPropagation();
userMenuDropdown.classList.toggle('show');
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.user-menu')) {
userMenuDropdown.classList.remove('show');
}
});
userMenuDropdown.addEventListener('click', (e) => {
e.stopPropagation();
});
document.getElementById('importOpmlButton').addEventListener('click', () => {
opmlFileInput.click();
userMenuDropdown.classList.remove('show');
});
document.getElementById('logoutButton').addEventListener('click', async () => {
try {
const response = await fetch('/logout', {
method: 'POST',
});
if (response.ok) {
window.location.href = '/login';
}
} catch (error) {
console.error('Logout failed:', error);
}
});
// Initialize feeds
handleFeeds();
}); });
// Fetch and display feeds // Fetch and display feeds
@ -348,46 +425,6 @@ async function handleFeeds() {
} }
} }
// Load feeds when page loads
document.addEventListener('DOMContentLoaded', handleFeeds);
// User menu functionality
const userMenuButton = document.getElementById('userMenuButton');
const userMenuDropdown = document.getElementById('userMenuDropdown');
// Toggle menu on button click
userMenuButton.addEventListener('click', (e) => {
e.stopPropagation();
userMenuDropdown.classList.toggle('show');
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.user-menu')) {
userMenuDropdown.classList.remove('show');
}
});
// Prevent menu from closing when clicking inside it
userMenuDropdown.addEventListener('click', (e) => {
e.stopPropagation();
});
// Logout functionality
document.getElementById('logoutButton').addEventListener('click', async () => {
try {
const response = await fetch('/logout', {
method: 'POST',
});
if (response.ok) {
window.location.href = '/login';
}
} catch (error) {
console.error('Logout failed:', error);
}
});
// Modal functionality // Modal functionality
const modal = document.getElementById('addFeedModal'); const modal = document.getElementById('addFeedModal');
const addFeedButton = document.getElementById('addFeedButton'); const addFeedButton = document.getElementById('addFeedButton');