Fix feed entry id issues

This commit is contained in:
Greg Shuflin 2025-02-05 02:31:29 -08:00
parent 44208470f8
commit c89ee29d4d
10 changed files with 58 additions and 52 deletions

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE feed_entries SET marked_read = NULL WHERE local_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "241fab33b2e6ffcba26cb3bd668f2f601d0ee2f24f5e8612323b66e5a2d2cbe9"
}

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT \n id as \"id!: String\",\n entry_id,\n title,\n published as \"published: Option<chrono::DateTime<Utc>>\",\n updated as \"updated: Option<chrono::DateTime<Utc>>\",\n summary,\n content,\n link,\n marked_read as \"marked_read: Option<chrono::DateTime<Utc>>\"\n FROM feed_entries \n WHERE feed_id = ?\n ORDER BY published DESC NULLS LAST\n LIMIT ?\n ", "query": "\n SELECT \n id as \"id!: String\",\n local_id as \"local_id!: String\",\n title,\n published as \"published: Option<chrono::DateTime<Utc>>\",\n updated as \"updated: Option<chrono::DateTime<Utc>>\",\n summary,\n content,\n link,\n marked_read as \"marked_read: Option<chrono::DateTime<Utc>>\"\n FROM feed_entries \n WHERE feed_id = ?\n ORDER BY published DESC NULLS LAST\n LIMIT ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -9,7 +9,7 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "entry_id", "name": "local_id!: String",
"ordinal": 1, "ordinal": 1,
"type_info": "Text" "type_info": "Text"
}, },
@ -64,5 +64,5 @@
true true
] ]
}, },
"hash": "7698fc853218a67cf22338697be3b504220e8fbf845e32945273c7e5cd579152" "hash": "2bbed6f20243ced25ac9359afefafb5ddffdff949250e0e4e35bf399fc0199fc"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT 1 as found FROM feed_entries e\n JOIN feeds f ON e.feed_id = f.feed_id\n WHERE e.id = ? AND f.user_id = ?\n ", "query": "\n SELECT 1 as found FROM feed_entries e\n JOIN feeds f ON e.feed_id = f.feed_id\n WHERE e.local_id = ? AND f.user_id = ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@ -16,5 +16,5 @@
false false
] ]
}, },
"hash": "ce12e0b02596a4045fb2cd91d566aa633109a65afcecc92564a785282e67e193" "hash": "3744bfed27760e0d6029063116352c42d90a3e4a5ea924e241437ce312535cc1"
} }

View File

@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n INSERT INTO feed_entries (\n id, feed_id, entry_id, title, published, updated, summary, content, link, created_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (feed_id, id) DO UPDATE SET\n title = excluded.title,\n published = excluded.published,\n updated = excluded.updated,\n summary = excluded.summary,\n content = excluded.content,\n link = excluded.link\n ", "query": "\n INSERT INTO feed_entries (\n id, feed_id, local_id, title, published, updated, summary, content, link, created_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (feed_id, id) DO UPDATE SET\n title = excluded.title,\n published = excluded.published,\n updated = excluded.updated,\n summary = excluded.summary,\n content = excluded.content,\n link = excluded.link\n ",
"describe": { "describe": {
"columns": [], "columns": [],
"parameters": { "parameters": {
@ -8,5 +8,5 @@
}, },
"nullable": [] "nullable": []
}, },
"hash": "3c8d358e534f35c6e59b1d94cf28175b5d9b60a662388dec1c20a79e298cac4f" "hash": "6efd0a1292d597cce5ea84e4fada30035dd40d557f5c305ec7b5bb6b0788948c"
} }

View File

@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE feed_entries SET marked_read = ? WHERE local_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "7cb047b30c2454689b447f4f2717b665d0094b645205e20390c6dec72fd15910"
}

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE feed_entries SET marked_read = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "ad2e09903009082ca746a9dc75cb04831b016d17cc7ce32fa86681431071fef6"
}

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE feed_entries SET marked_read = NULL WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "c64e0927e594985ee8d2be7190c8f76fea57c4c815981673c92e709b17b9204b"
}

View File

@ -0,0 +1,2 @@
-- Rename entry_id column to local_id
ALTER TABLE feed_entries RENAME COLUMN entry_id TO local_id;

View File

@ -23,8 +23,11 @@ pub struct FeedPollResponse {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
struct Entry { struct Entry {
id: Uuid, /// id is the id from the feed, and is the primary key of entries
entry_id: String, id: String,
/// local_id is a UUID generated locally. it is used because we don't have control over the
/// exact format of the id from the feed entry
local_id: Uuid,
title: String, title: String,
published: Option<DateTime<Utc>>, published: Option<DateTime<Utc>>,
updated: Option<DateTime<Utc>>, updated: Option<DateTime<Utc>>,
@ -74,11 +77,12 @@ async fn update_entry_db(
let published = entry.published.map(|dt| dt.to_rfc3339()); let published = entry.published.map(|dt| dt.to_rfc3339());
let updated = entry.updated.map(|dt| dt.to_rfc3339()); let updated = entry.updated.map(|dt| dt.to_rfc3339());
let entry_id = entry.id.to_string(); let local_id = entry.local_id.to_string();
let result = sqlx::query!( let result = sqlx::query!(
r#" r#"
INSERT INTO feed_entries ( INSERT INTO feed_entries (
id, feed_id, entry_id, title, published, updated, summary, content, link, created_at id, feed_id, local_id, title, published, updated, summary, content, link, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (feed_id, id) DO UPDATE SET ON CONFLICT (feed_id, id) DO UPDATE SET
title = excluded.title, title = excluded.title,
@ -88,9 +92,9 @@ async fn update_entry_db(
content = excluded.content, content = excluded.content,
link = excluded.link link = excluded.link
"#, "#,
entry_id, entry.id,
feed_id, feed_id,
entry.entry_id, local_id,
entry.title, entry.title,
published, published,
updated, updated,
@ -126,7 +130,7 @@ async fn read_entries(feed_id: &str, db: &mut SqliteConnection) -> Result<Vec<En
r#" r#"
SELECT SELECT
id as "id!: String", id as "id!: String",
entry_id, local_id as "local_id!: String",
title, title,
published as "published: Option<chrono::DateTime<Utc>>", published as "published: Option<chrono::DateTime<Utc>>",
updated as "updated: Option<chrono::DateTime<Utc>>", updated as "updated: Option<chrono::DateTime<Utc>>",
@ -158,8 +162,8 @@ async fn read_entries(feed_id: &str, db: &mut SqliteConnection) -> Result<Vec<En
.and_then(|s| serde::json::from_str(s).ok()); .and_then(|s| serde::json::from_str(s).ok());
Ok(Entry { Ok(Entry {
id: Uuid::parse_str(&row.id).map_err(|_| Status::InternalServerError)?, id: row.id.clone(),
entry_id: row.entry_id.clone(), local_id: Uuid::parse_str(&row.local_id).map_err(|_| Status::InternalServerError)?,
title: row.title.clone(), title: row.title.clone(),
published: row.published.flatten(), published: row.published.flatten(),
updated: row.updated.flatten(), updated: row.updated.flatten(),
@ -187,8 +191,8 @@ async fn fetch_new_entries(url: &Url) -> Result<Vec<Entry>, Status> {
.entries .entries
.into_iter() .into_iter()
.map(|feed_entry| Entry { .map(|feed_entry| Entry {
id: Uuid::new_v4(), id: feed_entry.id,
entry_id: feed_entry.id, local_id: Uuid::new_v4(),
title: get(feed_entry.title, "title"), title: get(feed_entry.title, "title"),
published: feed_entry.published, published: feed_entry.published,
updated: feed_entry.updated, updated: feed_entry.updated,
@ -242,10 +246,10 @@ pub async fn poll_feed(
Ok(Json(FeedPollResponse { count, entries })) Ok(Json(FeedPollResponse { count, entries }))
} }
#[patch("/entries/<entry_id>/state", data = "<state>")] #[patch("/entries/<local_id>/state", data = "<state>")]
pub async fn update_entry_state( pub async fn update_entry_state(
mut db: Connection<Db>, mut db: Connection<Db>,
entry_id: &str, local_id: &str,
user: AuthenticatedUser, user: AuthenticatedUser,
state: Json<EntryStateUpdate>, state: Json<EntryStateUpdate>,
) -> Result<Status, Status> { ) -> Result<Status, Status> {
@ -257,9 +261,9 @@ pub async fn update_entry_state(
r#" r#"
SELECT 1 as found FROM feed_entries e SELECT 1 as found FROM feed_entries e
JOIN feeds f ON e.feed_id = f.feed_id JOIN feeds f ON e.feed_id = f.feed_id
WHERE e.id = ? AND f.user_id = ? WHERE e.local_id = ? AND f.user_id = ?
"#, "#,
entry_id, local_id,
user_id, user_id,
) )
.fetch_optional(&mut **db) .fetch_optional(&mut **db)
@ -278,16 +282,16 @@ pub async fn update_entry_state(
let now = Utc::now(); let now = Utc::now();
let result = if read { let result = if read {
sqlx::query!( sqlx::query!(
"UPDATE feed_entries SET marked_read = ? WHERE id = ?", "UPDATE feed_entries SET marked_read = ? WHERE local_id = ?",
now, now,
entry_id local_id
) )
.execute(&mut **db) .execute(&mut **db)
.await .await
} else { } else {
sqlx::query!( sqlx::query!(
"UPDATE feed_entries SET marked_read = NULL WHERE id = ?", "UPDATE feed_entries SET marked_read = NULL WHERE local_id = ?",
entry_id local_id
) )
.execute(&mut **db) .execute(&mut **db)
.await .await

View File

@ -57,7 +57,7 @@ function renderFeedEntries(entries) {
: '<i class="fa-regular fa-square"></i>'; : '<i class="fa-regular fa-square"></i>';
readToggle.onclick = async () => { readToggle.onclick = async () => {
try { try {
const response = await fetch(`/entries/${entry.id}/state`, { const response = await fetch(`/entries/${entry.local_id}/state`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -90,7 +90,7 @@ function renderFeedEntries(entries) {
titleLink.onclick = () => { titleLink.onclick = () => {
if (!entry.marked_read) { if (!entry.marked_read) {
// Mark as read in the background, don't wait for it // Mark as read in the background, don't wait for it
fetch(`/entries/${entry.id}/state`, { fetch(`/entries/${entry.local_id}/state`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',