use crate::{ AdapterPool, Result, db::{ Channel, ChannelId, FeedId, Item, UserId, channel::UnparsedChannel, item::UnparsedItem, }, }; 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: &AdapterPool, id: FeedId ) -> Result { let feed = sqlx::query_as!( UnparsedFeed, "SELECT id, title FROM feeds WHERE id = ?", id.0 ).fetch_one(&pool.0).await?.parse(); feed } pub async fn create( pool: &AdapterPool, 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.0).await?.parse(); new_feed } pub async fn update_title( pool: &AdapterPool, id: FeedId, new_title: &str ) -> Result<()> { sqlx::query!( "UPDATE feeds SET title = ? WHERE id = ?", new_title, id.0 ).execute(&pool.0).await?; Ok(()) } pub async fn add_channel( &self, pool: &AdapterPool, channel_id: ChannelId ) -> Result<()> { let int_channel_id = i64::from(channel_id); sqlx::query!( "INSERT INTO feed_channels (feed_id, channel_id) VALUES (?, ?)", self.id.0, int_channel_id ).execute(&pool.0).await?; Ok(()) } pub async fn get_items( &self, pool: &AdapterPool, limit: u8, offset: u32 ) -> Result> { let items: Result> = sqlx::query_as!( UnparsedItem, "SELECT i.id as `id!`, i.channel_id, i.guid, i.fetched_at, i.title, i.description, i.content FROM items i JOIN feed_items fi on i.id = fi.item_id WHERE feed_id = ? AND archived = FALSE ORDER BY score DESC LIMIT ? OFFSET ?", self.id.0, limit, offset ).fetch_all(&pool.0).await?.into_iter().map(UnparsedItem::parse).collect(); items } pub async fn get_channels( &self, pool: &AdapterPool ) -> 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.0).await?.into_iter() .map(UnparsedChannel::parse).collect(); channels } } #[cfg(test)] mod tests { use super::*; use crate::{ Adapter, AdapterBuilder, db::User, }; async fn setup_adapter() -> Adapter { AdapterBuilder::new() .database_url("sqlite::memory:") .create().await.unwrap() } #[tokio::test] async fn create_feed() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); 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 adapter = setup_adapter().await; let pool = adapter.get_pool(); 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"); } }