db, add channel and feed; user.get_feeds
Adds the primary functionality for channel and feed Also adds a function to user to get the feeds associated with a user.
This commit is contained in:
parent
ce95a54227
commit
09b26c1b39
7 changed files with 1400 additions and 17 deletions
179
koucha/src/db/channel.rs
Normal file
179
koucha/src/db/channel.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
use reqwest::Url;
|
||||
use chrono::{DateTime, Utc};
|
||||
use crate::{
|
||||
Result,
|
||||
AdapterPool,
|
||||
db::ChannelKey,
|
||||
};
|
||||
|
||||
pub struct UnparsedChannel {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub link: String,
|
||||
pub description: Option<String>,
|
||||
pub last_fetched: Option<String>,
|
||||
}
|
||||
impl UnparsedChannel {
|
||||
pub fn parse(self) -> Result<Channel> {
|
||||
Ok(Channel {
|
||||
key: ChannelKey(self.id),
|
||||
title: self.title,
|
||||
link: Url::parse(&self.link)?,
|
||||
description: self.description,
|
||||
last_fetched: self.last_fetched.as_deref()
|
||||
.map(DateTime::parse_from_rfc2822)
|
||||
.transpose()?
|
||||
.map(|dt| dt.with_timezone(&Utc)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Channel {
|
||||
key: ChannelKey,
|
||||
title: String,
|
||||
link: Url,
|
||||
description: Option<String>,
|
||||
last_fetched: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
pub fn key(&self) -> ChannelKey { self.key }
|
||||
pub fn title(&self) -> &str { &self.title }
|
||||
pub fn link(&self) -> &Url { &self.link }
|
||||
pub fn description(&self) -> Option<&str> { self.description.as_deref() }
|
||||
pub fn last_fetched(&self) -> Option<DateTime<Utc>> { self.last_fetched }
|
||||
|
||||
pub async fn get_all(pool: &AdapterPool) -> Result<Vec<Self>> {
|
||||
sqlx::query_as!(
|
||||
UnparsedChannel,
|
||||
"SELECT id, title, link, description, last_fetched FROM channels"
|
||||
).fetch_all(&pool.0).await?.into_iter().map(UnparsedChannel::parse).collect()
|
||||
}
|
||||
|
||||
pub async fn get(pool: &AdapterPool, key: ChannelKey) -> Result<Self> {
|
||||
sqlx::query_as!(
|
||||
UnparsedChannel,
|
||||
"SELECT id, title, link, description, last_fetched
|
||||
FROM channels
|
||||
WHERE id = ?",
|
||||
key.0
|
||||
).fetch_one(&pool.0).await?.parse()
|
||||
}
|
||||
|
||||
pub async fn get_or_create(
|
||||
pool: &AdapterPool, link: Url
|
||||
) -> Result<Self> {
|
||||
let link_str = link.as_str();
|
||||
|
||||
sqlx::query_as!(
|
||||
UnparsedChannel,
|
||||
"INSERT INTO channels (title, link)
|
||||
VALUES(?, ?)
|
||||
ON CONFLICT(link) DO UPDATE SET link = link
|
||||
RETURNING id, title, link, description, last_fetched",
|
||||
link_str, link_str // We use the url as a placeholder title
|
||||
).fetch_one(&pool.0).await?.parse()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
test_utils::{
|
||||
FEED1, FEED2, CHANNEL_TITLE, CHANNEL_DESC,
|
||||
setup_adapter,
|
||||
setup_channel,
|
||||
},
|
||||
};
|
||||
use chrono::TimeZone;
|
||||
|
||||
#[test]
|
||||
fn parse_unparsed_item() {
|
||||
const CHANNEL_ID: i64 = 1;
|
||||
let date: DateTime<Utc> = Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap();
|
||||
|
||||
let raw_channel = UnparsedChannel {
|
||||
id: CHANNEL_ID,
|
||||
title: CHANNEL_TITLE.to_string(),
|
||||
link: FEED1.to_string(),
|
||||
description: Some(CHANNEL_DESC.to_string()),
|
||||
last_fetched: Some(date.to_rfc2822()),
|
||||
};
|
||||
let channel = raw_channel.parse().unwrap();
|
||||
|
||||
assert_eq!(channel.key.0, CHANNEL_ID);
|
||||
assert_eq!(channel.title, CHANNEL_TITLE);
|
||||
assert_eq!(channel.link.as_str(), FEED1);
|
||||
assert_eq!(channel.description, Some(CHANNEL_DESC.to_string()));
|
||||
assert_eq!(channel.last_fetched, Some(date));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_all() {
|
||||
let adapter = setup_adapter().await;
|
||||
let pool = adapter.get_pool();
|
||||
let url1 = Url::parse(FEED1).unwrap();
|
||||
let url2 = Url::parse(FEED2).unwrap();
|
||||
Channel::get_or_create(pool, url1).await.unwrap();
|
||||
Channel::get_or_create(pool, url2).await.unwrap();
|
||||
|
||||
let channels = Channel::get_all(pool).await.unwrap();
|
||||
assert_eq!(channels.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get() {
|
||||
let adapter = setup_adapter().await;
|
||||
let pool = adapter.get_pool();
|
||||
let channel_a = setup_channel(pool).await;
|
||||
|
||||
let channel_b = Channel::get(pool, channel_a.key()).await.unwrap();
|
||||
|
||||
assert_eq!(channel_a.key, channel_b.key);
|
||||
assert_eq!(channel_a.title, channel_b.title);
|
||||
assert_eq!(channel_a.link, channel_b.link);
|
||||
assert_eq!(channel_a.last_fetched, channel_b.last_fetched);
|
||||
assert_eq!(channel_a.description, channel_b.description);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create() {
|
||||
let adapter = setup_adapter().await;
|
||||
let pool = adapter.get_pool();
|
||||
let url_feed = Url::parse(FEED1).unwrap();
|
||||
|
||||
let channel = Channel::get_or_create(pool, url_feed).await.unwrap();
|
||||
|
||||
assert!(channel.key().0 > 0);
|
||||
assert_eq!(channel.link().as_str(), FEED1);
|
||||
assert!(channel.title().len() > 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_duplicate_returns_existing() {
|
||||
let adapter = setup_adapter().await;
|
||||
let pool = adapter.get_pool();
|
||||
let url_feed = Url::parse(FEED1).unwrap();
|
||||
|
||||
let channel1 = Channel::get_or_create(pool, url_feed.clone()).await.unwrap();
|
||||
let channel2 = Channel::get_or_create(pool, url_feed).await.unwrap();
|
||||
|
||||
assert_eq!(channel1.key(), channel2.key());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_all_channels() {
|
||||
let adapter = setup_adapter().await;
|
||||
let pool = adapter.get_pool();
|
||||
let url_feed1 = Url::parse(FEED1).unwrap();
|
||||
let url_feed2 = Url::parse(FEED2).unwrap();
|
||||
|
||||
Channel::get_or_create(pool, url_feed1).await.unwrap();
|
||||
Channel::get_or_create(pool, url_feed2).await.unwrap();
|
||||
|
||||
let channels = Channel::get_all(pool).await.unwrap();
|
||||
|
||||
assert_eq!(channels.len(), 2);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue