219 lines
5.2 KiB
Rust
219 lines
5.2 KiB
Rust
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<Feed> {
|
|
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<Self> {
|
|
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<Self> {
|
|
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<Vec<Item>> {
|
|
let items: Result<Vec<Item>> = 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<Vec<Channel>> {
|
|
let channels: Result<Vec<Channel>> = 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);
|
|
}
|
|
}
|