use crate::{ Result, AdapterPool, db::{ Channel, ChannelId, Feed, FeedId, Item, }, score::{ Score, Gravity, Boost, }, }; use chrono::{Utc, DateTime}; pub struct UnparsedFeedChannel { pub channel_id: i64, pub feed_id: i64, pub initial_score: Option, pub gravity: Option, pub boost: Option, } impl UnparsedFeedChannel { pub fn parse(self) -> Result { Ok(FeedChannel { channel_id: ChannelId(self.channel_id), feed_id: FeedId(self.feed_id), initial_score: Score::new(self.initial_score), gravity: Gravity::new(self.gravity), boost: Boost::new(self.boost), }) } } pub struct FeedChannel { channel_id: ChannelId, feed_id: FeedId, initial_score: Score, gravity: Gravity, boost: Boost, } impl FeedChannel { pub async fn get_channel(&self, pool: &AdapterPool) -> Result { Channel::get(pool, self.channel_id).await } pub async fn get_feed(&self, pool: &AdapterPool) -> Result { Feed::get(pool, self.feed_id).await } pub async fn add_item( &self, pool: &AdapterPool, item: &Item ) -> Result<()> { self.add_item_at(pool, item, Utc::now()).await } async fn add_item_at( &self, pool: &AdapterPool, item: &Item, add_at: DateTime ) -> Result<()> { let int_item_id = item.id().0; let int_initial_score = i64::from(self.initial_score); let string_last_updated = add_at.to_rfc2822(); sqlx::query!( "INSERT OR IGNORE INTO feed_items (feed_id, item_id, score, last_updated) VALUES (?, ?, ?, ?)", self.feed_id.0, int_item_id, int_initial_score, string_last_updated ).execute(&pool.0).await?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use reqwest::Url; use crate::{ db::{ Channel, FeedId, User }, test_utils::{ FEED1, setup_adapter, get_datetime }, }; #[test] fn parse() { const CID: i64 = 1; const FID: i64 = 2; const IS: i64 = 3; const G: i64 = 4; const B: i64 = 5; let ufc = UnparsedFeedChannel { channel_id: CID, feed_id: FID, initial_score: Some(IS), gravity: Some(G), boost: Some(B), }; let fc = ufc.parse().unwrap(); assert_eq!(fc.channel_id.0, CID); assert_eq!(fc.feed_id.0, FID); assert_eq!(i64::from(fc.initial_score), IS); assert_eq!(i64::from(fc.gravity), G); assert_eq!(i64::from(fc.boost), B); } // FeedChannel Tests #[tokio::test] async fn get_channel() { let adapter = setup_adapter().await; let pool = adapter.get_pool(); let url = Url::parse(FEED1).unwrap(); let channel = Channel::get_or_create(pool, url).await.unwrap(); let fc = FeedChannel { channel_id: channel.id(), feed_id: FeedId(1), // Fake Feed initial_score: Score::new(None), gravity: Gravity::new(None), boost: Boost::new(None), }; let channel_from_fc = fc.get_channel(pool).await.unwrap(); assert_eq!(channel_from_fc.id(), channel.id()); } #[tokio::test] async fn get_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(), "My Feed").await.unwrap(); let fc = FeedChannel { channel_id: ChannelId(1), // Fake Channel feed_id: feed.id(), initial_score: Score::new(None), gravity: Gravity::new(None), boost: Boost::new(None), }; let feed_from_fc = fc.get_feed(pool).await.unwrap(); assert_eq!(feed_from_fc.id(), feed.id()); } #[tokio::test] pub async fn add_item() { let dt = get_datetime(); 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(), "My Feed").await.unwrap(); let url = Url::parse(FEED1).unwrap(); let channel = Channel::get_or_create(pool, url).await.unwrap(); let fc = FeedChannel { channel_id: channel.id(), feed_id: feed.id(), initial_score: Score::new(None), gravity: Gravity::new(None), boost: Boost::new(None), }; let item = Item::get_or_create(pool, channel.id(), "item-guid").await.unwrap(); fc.add_item_at(pool, &item, dt).await.unwrap(); let items = feed.get_items(pool, 1, 0).await.unwrap(); assert_eq!(items[0].id(), item.id()); } }