Feed schema
This commit is contained in:
parent
608422eb4c
commit
73e5e2cd6e
50
.sqlx/query-f2c677b85d02ab2698c88013a5f0450a54ba4b29712b21cadb47971d38db012b.json
generated
Normal file
50
.sqlx/query-f2c677b85d02ab2698c88013a5f0450a54ba4b29712b21cadb47971d38db012b.json
generated
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"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 FROM feeds\n WHERE user_id = ?\n ORDER BY added_time DESC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "feed_id: String",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "url",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "user_id: String",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "added_time: chrono::DateTime<chrono::Utc>",
|
||||
"ordinal": 4,
|
||||
"type_info": "Datetime"
|
||||
},
|
||||
{
|
||||
"name": "last_checked_time: chrono::DateTime<chrono::Utc>",
|
||||
"ordinal": 5,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f2c677b85d02ab2698c88013a5f0450a54ba4b29712b21cadb47971d38db012b"
|
||||
}
|
14
migrations/20240320000002_create_feeds.sql
Normal file
14
migrations/20240320000002_create_feeds.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- Create feeds table
|
||||
CREATE TABLE feeds (
|
||||
feed_id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
added_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_checked_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX feeds_user_id_idx ON feeds(user_id);
|
||||
CREATE UNIQUE INDEX feeds_user_url_idx ON feeds(user_id, url);
|
155
src/feeds.rs
Normal file
155
src/feeds.rs
Normal file
@ -0,0 +1,155 @@
|
||||
use chrono;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::{json::Json, Deserialize, Serialize};
|
||||
use rocket_db_pools::Connection;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::Db;
|
||||
use crate::user::AuthenticatedUser;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Feed {
|
||||
feed_id: Uuid,
|
||||
name: String,
|
||||
url: String,
|
||||
user_id: Uuid,
|
||||
added_time: chrono::DateTime<chrono::Utc>,
|
||||
last_checked_time: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
impl Feed {
|
||||
pub fn new(name: String, url: String, user_id: Uuid) -> Self {
|
||||
let now = chrono::Utc::now();
|
||||
Feed {
|
||||
feed_id: Uuid::new_v4(),
|
||||
name,
|
||||
url,
|
||||
user_id,
|
||||
added_time: now,
|
||||
last_checked_time: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct NewFeed {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[post("/feeds", data = "<new_feed>")]
|
||||
pub async fn create_feed(
|
||||
mut db: Connection<Db>,
|
||||
new_feed: Json<NewFeed>,
|
||||
user: AuthenticatedUser,
|
||||
) -> Result<Json<Feed>, Status> {
|
||||
let new_feed = new_feed.into_inner();
|
||||
let feed = Feed::new(new_feed.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)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6)"
|
||||
)
|
||||
.bind(feed.feed_id.to_string())
|
||||
.bind(&feed.name)
|
||||
.bind(&feed.url)
|
||||
.bind(feed.user_id.to_string())
|
||||
.bind(feed.added_time.to_rfc3339())
|
||||
.bind(feed.last_checked_time.to_rfc3339())
|
||||
.execute(&mut **db)
|
||||
.await;
|
||||
|
||||
match query {
|
||||
Ok(_) => Ok(Json(feed)),
|
||||
Err(e) => {
|
||||
eprintln!("Database error: {}", e);
|
||||
match e {
|
||||
sqlx::Error::Database(db_err) if db_err.is_unique_violation() => {
|
||||
Err(Status::Conflict)
|
||||
}
|
||||
_ => Err(Status::InternalServerError),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/feeds")]
|
||||
pub async fn list_feeds(
|
||||
mut db: Connection<Db>,
|
||||
user: AuthenticatedUser,
|
||||
) -> Result<Json<Vec<Feed>>, Status> {
|
||||
let user_id = user.user_id.to_string();
|
||||
|
||||
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>"
|
||||
FROM feeds
|
||||
WHERE user_id = ?
|
||||
ORDER BY added_time DESC
|
||||
"#,
|
||||
user_id
|
||||
)
|
||||
.fetch_all(&mut **db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("Database error: {}", e);
|
||||
Status::InternalServerError
|
||||
})?;
|
||||
|
||||
let feeds = query
|
||||
.into_iter()
|
||||
.map(|row| Feed {
|
||||
feed_id: Uuid::parse_str(&row.feed_id).unwrap(),
|
||||
name: row.name,
|
||||
url: row.url,
|
||||
user_id: Uuid::parse_str(&row.user_id).unwrap(),
|
||||
added_time: row.added_time,
|
||||
last_checked_time: row.last_checked_time,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(feeds))
|
||||
}
|
||||
|
||||
#[delete("/feeds/<feed_id>")]
|
||||
pub async fn delete_feed(
|
||||
mut db: Connection<Db>,
|
||||
feed_id: &str,
|
||||
user: AuthenticatedUser,
|
||||
) -> Status {
|
||||
// Validate UUID format
|
||||
let feed_uuid = match Uuid::parse_str(feed_id) {
|
||||
Ok(uuid) => uuid,
|
||||
Err(_) => return Status::BadRequest,
|
||||
};
|
||||
|
||||
let query = sqlx::query(
|
||||
"DELETE FROM feeds WHERE feed_id = ? AND user_id = ?"
|
||||
)
|
||||
.bind(feed_uuid.to_string())
|
||||
.bind(user.user_id.to_string())
|
||||
.execute(&mut **db)
|
||||
.await;
|
||||
|
||||
match query {
|
||||
Ok(result) => {
|
||||
if result.rows_affected() > 0 {
|
||||
Status::NoContent
|
||||
} else {
|
||||
Status::NotFound
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Database error: {}", e);
|
||||
Status::InternalServerError
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
extern crate rocket;
|
||||
|
||||
mod user;
|
||||
mod feeds;
|
||||
|
||||
use rocket::fs::FileServer;
|
||||
use rocket::response::Redirect;
|
||||
@ -58,7 +59,10 @@ fn rocket() -> _ {
|
||||
user::get_users,
|
||||
user::delete_user,
|
||||
user::login,
|
||||
user::logout
|
||||
user::logout,
|
||||
feeds::create_feed,
|
||||
feeds::list_feeds,
|
||||
feeds::delete_feed,
|
||||
],
|
||||
)
|
||||
.mount("/static", FileServer::from("static"))
|
||||
|
Loading…
Reference in New Issue
Block a user