categorization of feeds
This commit is contained in:
parent
e80aca5e60
commit
ec2cdc98d5
37
migrations/20240320000003_add_feed_categorization.sql
Normal file
37
migrations/20240320000003_add_feed_categorization.sql
Normal file
@ -0,0 +1,37 @@
|
||||
-- Add categorization column
|
||||
ALTER TABLE feeds ADD COLUMN categorization JSON NOT NULL DEFAULT '[]';
|
||||
|
||||
-- Add CHECK constraint
|
||||
CREATE TRIGGER validate_feed_categorization
|
||||
AFTER INSERT ON feeds
|
||||
BEGIN
|
||||
SELECT CASE
|
||||
WHEN NOT (
|
||||
json_valid(NEW.categorization)
|
||||
AND json_type(NEW.categorization) = 'array'
|
||||
AND (
|
||||
SELECT COUNT(*)
|
||||
FROM json_each(NEW.categorization)
|
||||
WHERE json_type(value) != 'text'
|
||||
) = 0
|
||||
)
|
||||
THEN RAISE(ROLLBACK, 'categorization must be an array of strings')
|
||||
END;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER validate_feed_categorization_update
|
||||
AFTER UPDATE OF categorization ON feeds
|
||||
BEGIN
|
||||
SELECT CASE
|
||||
WHEN NOT (
|
||||
json_valid(NEW.categorization)
|
||||
AND json_type(NEW.categorization) = 'array'
|
||||
AND (
|
||||
SELECT COUNT(*)
|
||||
FROM json_each(NEW.categorization)
|
||||
WHERE json_type(value) != 'text'
|
||||
) = 0
|
||||
)
|
||||
THEN RAISE(ROLLBACK, 'categorization must be an array of strings')
|
||||
END;
|
||||
END;
|
38
src/feeds.rs
38
src/feeds.rs
@ -1,6 +1,7 @@
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::{json::Json, Deserialize, Serialize};
|
||||
use rocket::serde::{self, json::Json, Deserialize, Serialize};
|
||||
use rocket_db_pools::Connection;
|
||||
use sqlx::types::JsonValue;
|
||||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -16,6 +17,7 @@ pub struct Feed {
|
||||
user_id: Uuid,
|
||||
added_time: chrono::DateTime<chrono::Utc>,
|
||||
last_checked_time: chrono::DateTime<chrono::Utc>,
|
||||
categorization: Vec<String>,
|
||||
}
|
||||
|
||||
impl Feed {
|
||||
@ -28,6 +30,7 @@ impl Feed {
|
||||
user_id,
|
||||
added_time: now,
|
||||
last_checked_time: now,
|
||||
categorization: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,7 +38,9 @@ impl Feed {
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct NewFeed {
|
||||
pub url: Url, // Only URL is required now
|
||||
pub url: Url,
|
||||
#[serde(default)]
|
||||
pub categorization: Vec<String>,
|
||||
}
|
||||
|
||||
#[post("/feeds", data = "<new_feed>")]
|
||||
@ -74,11 +79,18 @@ pub async fn create_feed(
|
||||
.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 mut feed = Feed::new(name, new_feed.url, user.user_id);
|
||||
feed.categorization = new_feed.categorization;
|
||||
|
||||
// Convert categorization to JSON value
|
||||
let categorization_json = serde::json::to_value(&feed.categorization).map_err(|e| {
|
||||
eprintln!("Failed to serialize categorization: {}", e);
|
||||
Status::InternalServerError
|
||||
})?;
|
||||
|
||||
let query = sqlx::query(
|
||||
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
|
||||
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time, categorization)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, json(?7))",
|
||||
)
|
||||
.bind(feed.feed_id.to_string())
|
||||
.bind(&feed.name)
|
||||
@ -86,6 +98,7 @@ pub async fn create_feed(
|
||||
.bind(feed.user_id.to_string())
|
||||
.bind(feed.added_time.to_rfc3339())
|
||||
.bind(feed.last_checked_time.to_rfc3339())
|
||||
.bind(&categorization_json.to_string())
|
||||
.execute(&mut **db)
|
||||
.await;
|
||||
|
||||
@ -118,7 +131,8 @@ pub async fn list_feeds(
|
||||
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>"
|
||||
last_checked_time as "last_checked_time: chrono::DateTime<chrono::Utc>",
|
||||
categorization as "categorization: JsonValue"
|
||||
FROM feeds
|
||||
WHERE user_id = ?
|
||||
ORDER BY added_time DESC
|
||||
@ -136,7 +150,16 @@ pub async fn list_feeds(
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
// Parse URL from string
|
||||
let url = Url::parse(&row.url).map_err(|_| Status::InternalServerError)?;
|
||||
let url = Url::parse(&row.url).map_err(|e| {
|
||||
eprintln!("Failed to parse URL '{}': {}", row.url, e);
|
||||
Status::InternalServerError
|
||||
})?;
|
||||
|
||||
// Parse categorization from JSON value
|
||||
let categorization: Vec<String> = serde::json::from_value(row.categorization).map_err(|e| {
|
||||
eprintln!("Failed to parse categorization: {}", e);
|
||||
Status::InternalServerError
|
||||
})?;
|
||||
|
||||
Ok(Feed {
|
||||
feed_id: Uuid::parse_str(&row.feed_id).unwrap(),
|
||||
@ -145,6 +168,7 @@ pub async fn list_feeds(
|
||||
user_id: Uuid::parse_str(&row.user_id).unwrap(),
|
||||
added_time: row.added_time,
|
||||
last_checked_time: row.last_checked_time,
|
||||
categorization,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, Status>>()?;
|
||||
|
@ -43,6 +43,8 @@
|
||||
|
||||
<div class="sidebar">
|
||||
<h2>Feeds</h2>
|
||||
<div id="feedList">
|
||||
</div>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/message">Message</a></li>
|
||||
|
Loading…
Reference in New Issue
Block a user