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.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::{ db::User, test_utils::{ FEED_TITLE, USERNAME, FEED1, FEED2, setup_adapter, setup_feed, setup_channel, } }; use reqwest::Url; #[test] fn parse() { const FID: i64 = 1; let uf = UnparsedFeed { id: FID, title: FEED_TITLE.to_string(), }; let f = uf.parse().unwrap(); assert_eq!(f.id.0, FID); assert_eq!(f.title, FEED_TITLE); } #[tokio::test] async fn get() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); let feed = setup_feed(pool).await; let gotten_feed = Feed::get(pool, feed.id).await.unwrap(); assert_eq!(feed.id, gotten_feed.id); assert_eq!(feed.title, gotten_feed.title); } #[tokio::test] async fn create() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); let user = User::create(pool, USERNAME).await.unwrap(); let feed = Feed::create(pool, user.id(), FEED_TITLE).await.unwrap(); assert!(feed.id().0 > 0); assert_eq!(feed.title(), FEED_TITLE); } #[tokio::test] async fn update_title() { const NEW_FEED_TITLE: &str = "My NEW feed!"; let adapter = setup_adapter().await; let pool = adapter.get_pool(); let feed = setup_feed(pool).await; Feed::update_title(pool, feed.id(), NEW_FEED_TITLE).await.unwrap(); let updated = Feed::get(pool, feed.id()).await.unwrap(); assert_eq!(updated.title(), NEW_FEED_TITLE); } #[tokio::test] async fn add_channel() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); let feed = setup_feed(pool).await; let channel = setup_channel(pool).await; feed.add_channel(pool, channel.id()).await.unwrap(); let channels = feed.get_channels(pool).await.unwrap(); let gotten_channel = &channels[0]; assert_eq!(gotten_channel.id().0, channel.id().0); } #[tokio::test] async fn get_channels() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); let feed = setup_feed(pool).await; let url1 = Url::parse(FEED1).unwrap(); let channel1 = Channel::get_or_create(pool, url1).await.unwrap(); let url2 = Url::parse(FEED2).unwrap(); let channel2 = Channel::get_or_create(pool, url2).await.unwrap(); feed.add_channel(pool, channel1.id()).await.unwrap(); feed.add_channel(pool, channel2.id()).await.unwrap(); let channels = feed.get_channels(pool).await.unwrap(); assert_eq!(channels.len(), 2); } }