This commit is contained in:
Greg Shuflin 2025-02-02 03:25:51 -08:00
parent e27fb1964f
commit 13597cd9da
4 changed files with 83 additions and 17 deletions

View File

@ -4,6 +4,7 @@ use rocket::serde::{json::Json, Deserialize, Serialize};
use rocket_db_pools::Connection;
use uuid::Uuid;
use url::Url;
use reqwest;
use crate::Db;
use crate::user::AuthenticatedUser;
@ -36,8 +37,7 @@ impl Feed {
#[derive(Debug, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct NewFeed {
pub name: String,
pub url: Url, // Changed from String to Url
pub url: Url, // Only URL is required now
}
#[post("/feeds", data = "<new_feed>")]
@ -47,13 +47,41 @@ pub async fn create_feed(
user: AuthenticatedUser,
) -> Result<Json<Feed>, Status> {
let new_feed = new_feed.into_inner();
// URL validation only needs to check scheme now
if !new_feed.url.scheme().eq("http") && !new_feed.url.scheme().eq("https") {
eprintln!("Invalid URL scheme: {}", new_feed.url.scheme());
return Err(Status::UnprocessableEntity);
}
let feed = Feed::new(new_feed.name, new_feed.url, user.user_id);
// Fetch the feed content
let response = reqwest::get(new_feed.url.as_ref())
.await
.map_err(|e| {
eprintln!("Failed to fetch feed: {}", e);
Status::UnprocessableEntity
})?;
let content = response.text()
.await
.map_err(|e| {
eprintln!("Failed to read response body: {}", e);
Status::UnprocessableEntity
})?;
// Parse the feed
let feed_data = feed_rs::parser::parse(content.as_bytes())
.map_err(|e| {
eprintln!("Failed to parse feed content: {}", e);
Status::UnprocessableEntity
})?;
// Use the feed title as the name, or URL if no title is available
let name = feed_data.title
.map(|t| t.content)
.unwrap_or_else(|| new_feed.url.host_str().unwrap_or("Unknown").to_string());
let feed = Feed::new(name, new_feed.url, user.user_id);
let query = sqlx::query(
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time)

View File

@ -260,4 +260,30 @@ body.with-sidebar {
.sidebar {
top: 3rem;
}
/* Spinner and loading state */
.spinner {
width: 30px;
height: 30px;
margin: 0 auto 10px;
border: 3px solid #f3f3f3;
border-top: 3px solid var(--primary-red);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-message {
padding: 1rem;
}
/* Button states */
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}

View File

@ -20,22 +20,35 @@ const cancelButton = document.getElementById('cancelAddFeed');
const confirmButton = document.getElementById('confirmAddFeed');
const addFeedForm = document.getElementById('addFeedForm');
const errorMessage = document.getElementById('feedErrorMessage');
const loadingMessage = document.getElementById('loadingMessage');
function showModal() {
modal.classList.add('show');
errorMessage.style.display = 'none';
loadingMessage.style.display = 'none';
addFeedForm.reset();
confirmButton.disabled = false;
}
function hideModal() {
modal.classList.remove('show');
errorMessage.style.display = 'none';
loadingMessage.style.display = 'none';
addFeedForm.reset();
confirmButton.disabled = false;
}
function showError(message) {
errorMessage.textContent = message;
errorMessage.style.display = 'block';
loadingMessage.style.display = 'none';
confirmButton.disabled = false;
}
function showLoading() {
loadingMessage.style.display = 'block';
errorMessage.style.display = 'none';
confirmButton.disabled = true;
}
addFeedButton.addEventListener('click', showModal);
@ -49,21 +62,22 @@ modal.addEventListener('click', (e) => {
});
confirmButton.addEventListener('click', async () => {
const name = document.getElementById('feedName').value.trim();
const url = document.getElementById('feedUrl').value.trim();
if (!name || !url) {
showError('Please fill in all fields');
if (!url) {
showError('Please enter a feed URL');
return;
}
showLoading();
try {
const response = await fetch('/feeds', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, url }),
body: JSON.stringify({ url }),
});
if (response.ok) {
@ -72,13 +86,12 @@ confirmButton.addEventListener('click', async () => {
// For now, we'll just reload the page
window.location.reload();
} else {
const errorText = await response.text();
switch (response.status) {
case 409:
showError('You already have this feed added');
break;
case 422:
showError('Invalid feed URL. Please make sure it starts with http:// or https://');
showError('Invalid feed URL. Please make sure it\'s a valid RSS or Atom feed.');
break;
default:
showError('Failed to add feed. Please try again.');

View File

@ -22,22 +22,21 @@
</div>
<div class="modal-body">
<form id="addFeedForm">
<div class="form-group">
<label for="feedName">Feed Name</label>
<input type="text" id="feedName" name="name" required
placeholder="My Blog Feed">
</div>
<div class="form-group">
<label for="feedUrl">Feed URL</label>
<input type="url" id="feedUrl" name="url" required
placeholder="https://example.com/feed.xml">
</div>
<div class="error-message" id="feedErrorMessage" style="display: none; color: red; margin-bottom: 10px;"></div>
<div class="loading-message" id="loadingMessage" style="display: none; color: #666; margin-bottom: 10px; text-align: center;">
<div class="spinner"></div>
<div>Loading feed information...</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn-secondary" id="cancelAddFeed">Cancel</button>
<button type="button" class="btn-primary" id="confirmAddFeed">OK</button>
<button type="button" class="btn-primary" id="confirmAddFeed">Add Feed</button>
</div>
</div>
</div>