Start working on unread articles
This commit is contained in:
parent
0016ef97bb
commit
6fccda5827
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"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": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@ -37,6 +37,11 @@
|
|||||||
"name": "categorization: JsonValue",
|
"name": "categorization: JsonValue",
|
||||||
"ordinal": 6,
|
"ordinal": 6,
|
||||||
"type_info": "Null"
|
"type_info": "Null"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "unread_count!: i64",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Int"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@ -49,8 +54,9 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "7c3a826ac9b9105554bed433100db0435c55fca5c239b4e5f58380e14697c3a0"
|
"hash": "d73f4bc0e8844f0a5dd4f897a1a1f1071db67502d23f7d6d98adc3da2241d57b"
|
||||||
}
|
}
|
31
src/feeds.rs
31
src/feeds.rs
@ -21,6 +21,7 @@ pub struct Feed {
|
|||||||
pub added_time: chrono::DateTime<chrono::Utc>,
|
pub added_time: chrono::DateTime<chrono::Utc>,
|
||||||
pub last_checked_time: chrono::DateTime<chrono::Utc>,
|
pub last_checked_time: chrono::DateTime<chrono::Utc>,
|
||||||
pub categorization: Vec<String>,
|
pub categorization: Vec<String>,
|
||||||
|
pub unread_count: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Feed {
|
impl Feed {
|
||||||
@ -34,6 +35,7 @@ impl Feed {
|
|||||||
added_time: now,
|
added_time: now,
|
||||||
last_checked_time: chrono::DateTime::UNIX_EPOCH,
|
last_checked_time: chrono::DateTime::UNIX_EPOCH,
|
||||||
categorization: Vec::new(),
|
categorization: Vec::new(),
|
||||||
|
unread_count: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,16 +137,19 @@ pub async fn list_feeds(
|
|||||||
let query = sqlx::query!(
|
let query = sqlx::query!(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
feed_id as "feed_id: String",
|
f.feed_id as "feed_id: String",
|
||||||
name,
|
f.name,
|
||||||
url,
|
f.url,
|
||||||
user_id as "user_id: String",
|
f.user_id as "user_id: String",
|
||||||
added_time as "added_time: chrono::DateTime<chrono::Utc>",
|
f.added_time as "added_time: chrono::DateTime<chrono::Utc>",
|
||||||
last_checked_time as "last_checked_time: chrono::DateTime<chrono::Utc>",
|
f.last_checked_time as "last_checked_time: chrono::DateTime<chrono::Utc>",
|
||||||
categorization as "categorization: JsonValue"
|
f.categorization as "categorization: JsonValue",
|
||||||
FROM feeds
|
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"
|
||||||
WHERE user_id = ?
|
FROM feeds f
|
||||||
ORDER BY name ASC
|
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
|
user_id
|
||||||
)
|
)
|
||||||
@ -187,6 +192,7 @@ pub async fn list_feeds(
|
|||||||
added_time: row.added_time,
|
added_time: row.added_time,
|
||||||
last_checked_time: row.last_checked_time,
|
last_checked_time: row.last_checked_time,
|
||||||
categorization,
|
categorization,
|
||||||
|
unread_count: row.unread_count,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, Status>>()?;
|
.collect::<Result<Vec<_>, Status>>()?;
|
||||||
@ -291,6 +297,7 @@ pub async fn get_feed(
|
|||||||
added_time: row.added_time,
|
added_time: row.added_time,
|
||||||
last_checked_time: row.last_checked_time,
|
last_checked_time: row.last_checked_time,
|
||||||
categorization,
|
categorization,
|
||||||
|
unread_count: 0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,11 +320,11 @@ mod tests {
|
|||||||
assert_eq!(feed.last_checked_time, chrono::DateTime::UNIX_EPOCH);
|
assert_eq!(feed.last_checked_time, chrono::DateTime::UNIX_EPOCH);
|
||||||
|
|
||||||
// Feed ID should be a valid UUID
|
// 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)
|
// Added time should be recent (within last few seconds)
|
||||||
let now = chrono::Utc::now();
|
let now = chrono::Utc::now();
|
||||||
let time_diff = now - feed.added_time;
|
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")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct FeedPollResponse {
|
pub struct FeedPollResponse {
|
||||||
count: usize,
|
count: usize,
|
||||||
|
unread_count: usize,
|
||||||
entries: Vec<Entry>,
|
entries: Vec<Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,9 +245,17 @@ pub async fn poll_feed(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let count = entries.len();
|
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>")]
|
#[patch("/entries/<local_id>/state", data = "<state>")]
|
||||||
|
@ -101,3 +101,15 @@
|
|||||||
background-color: rgba(244, 63, 63, 0.1);
|
background-color: rgba(244, 63, 63, 0.1);
|
||||||
border-radius: 4px;
|
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';
|
spinner.className = 'feed-spinner';
|
||||||
name.appendChild(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.onclick = async () => {
|
||||||
name.classList.add('loading');
|
name.classList.add('loading');
|
||||||
try {
|
try {
|
||||||
@ -158,6 +167,14 @@ function openFeed(feed) {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Feed poll response:', data);
|
console.log('Feed poll response:', data);
|
||||||
renderFeedEntries(data.entries);
|
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
|
// Update the top bar title
|
||||||
const topBarTitle = document.querySelector('.top-bar-title');
|
const topBarTitle = document.querySelector('.top-bar-title');
|
||||||
topBarTitle.innerHTML = `RSS Reader <span class="feed-title-separator">-</span> ${feed.name}`;
|
topBarTitle.innerHTML = `RSS Reader <span class="feed-title-separator">-</span> ${feed.name}`;
|
||||||
|
Loading…
Reference in New Issue
Block a user