Start working on unread articles
This commit is contained in:
parent
0016ef97bb
commit
6fccda5827
@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT \n feed_id as \"feed_id: String\",\n name,\n url,\n user_id as \"user_id: String\",\n added_time as \"added_time: chrono::DateTime<chrono::Utc>\",\n last_checked_time as \"last_checked_time: chrono::DateTime<chrono::Utc>\",\n categorization as \"categorization: JsonValue\"\n FROM feeds\n WHERE user_id = ?\n ORDER BY name ASC\n ",
|
||||
"query": "\n SELECT \n f.feed_id as \"feed_id: String\",\n f.name,\n f.url,\n f.user_id as \"user_id: String\",\n f.added_time as \"added_time: chrono::DateTime<chrono::Utc>\",\n f.last_checked_time as \"last_checked_time: chrono::DateTime<chrono::Utc>\",\n f.categorization as \"categorization: JsonValue\",\n COALESCE(SUM(CASE WHEN e.id IS NOT NULL AND e.marked_read IS NULL THEN 1 ELSE 0 END), 0) as \"unread_count!: i64\"\n FROM feeds f\n LEFT JOIN feed_entries e ON f.feed_id = e.feed_id\n WHERE f.user_id = ?\n GROUP BY f.feed_id, f.name, f.url, f.user_id, f.added_time, f.last_checked_time, f.categorization\n ORDER BY f.name ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@ -37,6 +37,11 @@
|
||||
"name": "categorization: JsonValue",
|
||||
"ordinal": 6,
|
||||
"type_info": "Null"
|
||||
},
|
||||
{
|
||||
"name": "unread_count!: i64",
|
||||
"ordinal": 7,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@ -49,8 +54,9 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "7c3a826ac9b9105554bed433100db0435c55fca5c239b4e5f58380e14697c3a0"
|
||||
"hash": "d73f4bc0e8844f0a5dd4f897a1a1f1071db67502d23f7d6d98adc3da2241d57b"
|
||||
}
|
35
src/feeds.rs
35
src/feeds.rs
@ -21,6 +21,7 @@ pub struct Feed {
|
||||
pub added_time: chrono::DateTime<chrono::Utc>,
|
||||
pub last_checked_time: chrono::DateTime<chrono::Utc>,
|
||||
pub categorization: Vec<String>,
|
||||
pub unread_count: i64,
|
||||
}
|
||||
|
||||
impl Feed {
|
||||
@ -34,6 +35,7 @@ impl Feed {
|
||||
added_time: now,
|
||||
last_checked_time: chrono::DateTime::UNIX_EPOCH,
|
||||
categorization: Vec::new(),
|
||||
unread_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,16 +137,19 @@ pub async fn list_feeds(
|
||||
let query = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
feed_id as "feed_id: String",
|
||||
name,
|
||||
url,
|
||||
user_id as "user_id: String",
|
||||
added_time as "added_time: chrono::DateTime<chrono::Utc>",
|
||||
last_checked_time as "last_checked_time: chrono::DateTime<chrono::Utc>",
|
||||
categorization as "categorization: JsonValue"
|
||||
FROM feeds
|
||||
WHERE user_id = ?
|
||||
ORDER BY name ASC
|
||||
f.feed_id as "feed_id: String",
|
||||
f.name,
|
||||
f.url,
|
||||
f.user_id as "user_id: String",
|
||||
f.added_time as "added_time: chrono::DateTime<chrono::Utc>",
|
||||
f.last_checked_time as "last_checked_time: chrono::DateTime<chrono::Utc>",
|
||||
f.categorization as "categorization: JsonValue",
|
||||
COALESCE(SUM(CASE WHEN e.id IS NOT NULL AND e.marked_read IS NULL THEN 1 ELSE 0 END), 0) as "unread_count!: i64"
|
||||
FROM feeds f
|
||||
LEFT JOIN feed_entries e ON f.feed_id = e.feed_id
|
||||
WHERE f.user_id = ?
|
||||
GROUP BY f.feed_id, f.name, f.url, f.user_id, f.added_time, f.last_checked_time, f.categorization
|
||||
ORDER BY f.name ASC
|
||||
"#,
|
||||
user_id
|
||||
)
|
||||
@ -187,6 +192,7 @@ pub async fn list_feeds(
|
||||
added_time: row.added_time,
|
||||
last_checked_time: row.last_checked_time,
|
||||
categorization,
|
||||
unread_count: row.unread_count,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, Status>>()?;
|
||||
@ -291,6 +297,7 @@ pub async fn get_feed(
|
||||
added_time: row.added_time,
|
||||
last_checked_time: row.last_checked_time,
|
||||
categorization,
|
||||
unread_count: 0,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -311,13 +318,13 @@ mod tests {
|
||||
assert_eq!(feed.user_id, user_id);
|
||||
assert_eq!(feed.categorization, Vec::<String>::new());
|
||||
assert_eq!(feed.last_checked_time, chrono::DateTime::UNIX_EPOCH);
|
||||
|
||||
|
||||
// Feed ID should be a valid UUID
|
||||
assert!(feed.feed_id.to_string().len() == 36); // UUID string length
|
||||
|
||||
assert!(feed.feed_id.to_string().len() == 36); // UUID string length
|
||||
|
||||
// Added time should be recent (within last few seconds)
|
||||
let now = chrono::Utc::now();
|
||||
let time_diff = now - feed.added_time;
|
||||
assert!(time_diff.num_seconds().abs() < 5); // Allow 5 second difference
|
||||
assert!(time_diff.num_seconds().abs() < 5); // Allow 5 second difference
|
||||
}
|
||||
}
|
||||
|
13
src/poll.rs
13
src/poll.rs
@ -17,6 +17,7 @@ const MAX_ENTRIES_PER_FEED: i32 = 30;
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct FeedPollResponse {
|
||||
count: usize,
|
||||
unread_count: usize,
|
||||
entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
@ -244,9 +245,17 @@ pub async fn poll_feed(
|
||||
};
|
||||
|
||||
let count = entries.len();
|
||||
info!("Returning {} entries for feed {}", count, feed_id);
|
||||
let unread_count = entries.iter().filter(|e| e.marked_read.is_none()).count();
|
||||
info!(
|
||||
"Returning {} entries ({} unread) for feed {}",
|
||||
count, unread_count, feed_id
|
||||
);
|
||||
|
||||
Ok(Json(FeedPollResponse { count, entries }))
|
||||
Ok(Json(FeedPollResponse {
|
||||
count,
|
||||
unread_count,
|
||||
entries,
|
||||
}))
|
||||
}
|
||||
|
||||
#[patch("/entries/<local_id>/state", data = "<state>")]
|
||||
|
@ -100,4 +100,16 @@
|
||||
padding: 1rem;
|
||||
background-color: rgba(244, 63, 63, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.feed-unread-count {
|
||||
display: none;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 2px 6px;
|
||||
font-size: 0.8rem;
|
||||
margin-left: 8px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
@ -148,6 +148,15 @@ function openFeed(feed) {
|
||||
spinner.className = 'feed-spinner';
|
||||
name.appendChild(spinner);
|
||||
|
||||
// Create unread count element
|
||||
const unreadCount = document.createElement('span');
|
||||
unreadCount.className = 'feed-unread-count';
|
||||
if (feed.unread_count > 0) {
|
||||
unreadCount.textContent = feed.unread_count;
|
||||
unreadCount.style.display = 'inline';
|
||||
}
|
||||
name.appendChild(unreadCount);
|
||||
|
||||
name.onclick = async () => {
|
||||
name.classList.add('loading');
|
||||
try {
|
||||
@ -158,6 +167,14 @@ function openFeed(feed) {
|
||||
const data = await response.json();
|
||||
console.log('Feed poll response:', data);
|
||||
renderFeedEntries(data.entries);
|
||||
// Update the unread count
|
||||
feed.unread_count = data.unread_count;
|
||||
if (feed.unread_count > 0) {
|
||||
unreadCount.textContent = feed.unread_count;
|
||||
unreadCount.style.display = 'inline';
|
||||
} else {
|
||||
unreadCount.style.display = 'none';
|
||||
}
|
||||
// Update the top bar title
|
||||
const topBarTitle = document.querySelector('.top-bar-title');
|
||||
topBarTitle.innerHTML = `RSS Reader <span class="feed-title-separator">-</span> ${feed.name}`;
|
||||
|
Loading…
Reference in New Issue
Block a user