use crate::{ Result, Item, Channel, channel::{ UnparsedChannel, ChannelId, }, user::UserId, }; use sqlx::SqlitePool; #[derive(Copy, Clone)] pub struct FeedId(i64); impl From for i64 { fn from(id: FeedId) -> Self { id.0 } } pub struct UnparsedFeed { pub id: i64, pub title: String, } impl UnparsedFeed { pub fn parse(self) -> Result { Ok(Feed { id: FeedId(self.id), title: self.title, }) } } pub struct Feed { id: FeedId, title: String, } impl Feed { pub fn id(&self) -> FeedId { self.id } pub fn title(&self) -> &str { &self.title } pub async fn get( pool: &SqlitePool, id: FeedId ) -> Result { let feed = sqlx::query_as!( UnparsedFeed, "SELECT id, title FROM feeds WHERE id = ?", id.0 ).fetch_one(pool).await?.parse(); feed } pub async fn create( pool: &SqlitePool, user_id: UserId, title: &str ) -> Result { let int_id = i64::from(user_id); let new_feed = sqlx::query_as!( UnparsedFeed, "INSERT INTO feeds (user_id, title) VALUES (?, ?) RETURNING id as `id!`, title", int_id, title ).fetch_one(pool).await?.parse(); new_feed } pub async fn update_title( pool: &SqlitePool, id: FeedId, new_title: &str ) -> Result<()> { sqlx::query!( "UPDATE feeds SET title = ? WHERE id = ?", new_title, id.0 ).execute(pool).await?; Ok(()) } pub async fn add_channel( &self, pool: &SqlitePool, channel_id: ChannelId ) -> Result<()> { sqlx::query!( "INSERT INTO feed_channels (feed_id, channel_id) VALUES (?, ?)", self.id.0, channel_id.0 ).execute(pool).await?; Ok(()) } pub async fn get_items( &self, pool: &SqlitePool, limit: u8, offset: u32 ) -> Result> { let items = sqlx::query_as!( Item, "SELECT item_id as id FROM feed_items WHERE feed_id = ? AND archived = FALSE ORDER BY score DESC LIMIT ? OFFSET ?", self.id.0, limit, offset ).fetch_all(pool).await?; Ok(items) } pub async fn get_channels( &self, pool: &SqlitePool ) -> Result> { let channels: Result> = sqlx::query_as!( UnparsedChannel, "SELECT c.id as `id!`, c.title, c.link, c.description, c.last_fetched FROM channels c JOIN feed_channels fc on c.id = fc.channel_id WHERE fc.feed_id = ?", self.id.0 ).fetch_all(pool).await?.into_iter() .map(UnparsedChannel::parse).collect(); channels } } #[cfg(test)] mod tests { use super::*; use crate::User; use sqlx::SqlitePool; async fn setup_test_db() -> SqlitePool { let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); sqlx::migrate!().run(&pool).await.unwrap(); pool } #[tokio::test] async fn create_feed() { let pool = setup_test_db().await; let user = User::create(&pool, "Alice").await.unwrap(); let feed = Feed::create(&pool, user.id(), "Tech News").await.unwrap(); assert_eq!(feed.title(), "Tech News"); assert!(feed.id().0 > 0); } #[tokio::test] async fn test_update_title() { let pool = setup_test_db().await; let user = User::create(&pool, "Alice").await.unwrap(); let feed = Feed::create(&pool, user.id(), "Tech News").await.unwrap(); Feed::update_title(&pool, feed.id(), "Technology").await.unwrap(); let updated = Feed::get(&pool, feed.id()).await.unwrap(); assert_eq!(updated.title(), "Technology"); } }