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
973
koucha/Cargo.lock
generated
973
koucha/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
reqwest = "0.13.1"
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
sqlx = { version = "0.8.6", features = [ "runtime-tokio", "sqlite" ] }
|
sqlx = { version = "0.8.6", features = [ "runtime-tokio", "sqlite" ] }
|
||||||
chrono = "0.4.43"
|
chrono = "0.4.43"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
mod user;
|
mod user;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
|
mod feed;
|
||||||
|
pub use feed::Feed;
|
||||||
|
mod channel;
|
||||||
|
pub use channel::Channel;
|
||||||
|
|
||||||
macro_rules! define_key {
|
macro_rules! define_key {
|
||||||
($name:ident) => {
|
($name:ident) => {
|
||||||
|
|
@ -9,3 +13,5 @@ macro_rules! define_key {
|
||||||
}
|
}
|
||||||
|
|
||||||
define_key!(UserKey);
|
define_key!(UserKey);
|
||||||
|
define_key!(FeedKey);
|
||||||
|
define_key!(ChannelKey);
|
||||||
|
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
koucha/src/db/feed.rs
Normal file
191
koucha/src/db/feed.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
use crate::{
|
||||||
|
AdapterPool,
|
||||||
|
Result,
|
||||||
|
db::{
|
||||||
|
Channel,
|
||||||
|
ChannelKey,
|
||||||
|
FeedKey,
|
||||||
|
UserKey,
|
||||||
|
channel::UnparsedChannel,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct UnparsedFeed {
|
||||||
|
pub id: i64,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
impl UnparsedFeed {
|
||||||
|
pub fn parse(self) -> Result<Feed> {
|
||||||
|
Ok(Feed {
|
||||||
|
key: FeedKey(self.id),
|
||||||
|
title: self.title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Feed {
|
||||||
|
key: FeedKey,
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Feed {
|
||||||
|
pub fn key(&self) -> FeedKey { self.key }
|
||||||
|
pub fn title(&self) -> &str { &self.title }
|
||||||
|
|
||||||
|
pub async fn get(
|
||||||
|
pool: &AdapterPool, key: FeedKey
|
||||||
|
) -> Result<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
UnparsedFeed,
|
||||||
|
"SELECT id, title FROM feeds WHERE id = ?",
|
||||||
|
key.0
|
||||||
|
).fetch_one(&pool.0).await?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
pool: &AdapterPool, user_key: UserKey, title: &str
|
||||||
|
) -> Result<Self> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
UnparsedFeed,
|
||||||
|
"INSERT INTO feeds (user_id, title)
|
||||||
|
VALUES (?, ?)
|
||||||
|
RETURNING id as `id!`, title",
|
||||||
|
user_key.0, title
|
||||||
|
).fetch_one(&pool.0).await?.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_title(
|
||||||
|
pool: &AdapterPool, key: FeedKey, new_title: &str
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE feeds SET title = ? WHERE id = ?",
|
||||||
|
new_title, key.0
|
||||||
|
).execute(&pool.0).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_channel(
|
||||||
|
&self, pool: &AdapterPool, channel_key: ChannelKey
|
||||||
|
) -> Result<()> {
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO feed_channels (feed_id, channel_id)
|
||||||
|
VALUES (?, ?)",
|
||||||
|
self.key.0, channel_key.0
|
||||||
|
).execute(&pool.0).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_channels(
|
||||||
|
&self, pool: &AdapterPool
|
||||||
|
) -> 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.key.0
|
||||||
|
).fetch_all(&pool.0).await?.into_iter()
|
||||||
|
.map(UnparsedChannel::parse).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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.key.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.key).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(feed.key, gotten_feed.key);
|
||||||
|
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.key(), FEED_TITLE).await.unwrap();
|
||||||
|
|
||||||
|
assert!(feed.key().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.key(), NEW_FEED_TITLE).await.unwrap();
|
||||||
|
|
||||||
|
let updated = Feed::get(pool, feed.key()).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.key()).await.unwrap();
|
||||||
|
|
||||||
|
let channels = feed.get_channels(pool).await.unwrap();
|
||||||
|
let gotten_channel = &channels[0];
|
||||||
|
|
||||||
|
assert_eq!(gotten_channel.key().0, channel.key().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.key()).await.unwrap();
|
||||||
|
feed.add_channel(pool, channel2.key()).await.unwrap();
|
||||||
|
|
||||||
|
let channels = feed.get_channels(pool).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(channels.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Result,
|
Result,
|
||||||
AdapterPool,
|
AdapterPool,
|
||||||
db::UserKey,
|
db::{
|
||||||
|
UserKey,
|
||||||
|
Feed,
|
||||||
|
feed::UnparsedFeed,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct UnparsedUser {
|
pub struct UnparsedUser {
|
||||||
|
|
@ -61,14 +65,26 @@ impl User {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_feeds(&self, pool: &AdapterPool) -> Result<Vec<Feed>> {
|
||||||
|
let feeds: Result<Vec<Feed>> = sqlx::query_as!(
|
||||||
|
UnparsedFeed,
|
||||||
|
"SELECT id, title FROM feeds WHERE user_id = ?",
|
||||||
|
self.key.0
|
||||||
|
).fetch_all(&pool.0).await?.into_iter()
|
||||||
|
.map(UnparsedFeed::parse).collect();
|
||||||
|
|
||||||
|
feeds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
db::Feed,
|
||||||
test_utils::{
|
test_utils::{
|
||||||
USERNAME, USERNAME2,
|
USERNAME, USERNAME2, FEED_TITLE, FEED_TITLE2,
|
||||||
setup_adapter,
|
setup_adapter,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -157,4 +173,28 @@ mod tests {
|
||||||
|
|
||||||
assert!(status.is_err());
|
assert!(status.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_feeds() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
let user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
Feed::create(pool, user.key, FEED_TITLE).await.unwrap();
|
||||||
|
Feed::create(pool, user.key, FEED_TITLE2).await.unwrap();
|
||||||
|
|
||||||
|
let feeds = user.get_feeds(pool).await.unwrap();
|
||||||
|
assert_eq!(feeds.len(), 2);
|
||||||
|
assert!(feeds.iter().any(|f| f.title() == FEED_TITLE));
|
||||||
|
assert!(feeds.iter().any(|f| f.title() == FEED_TITLE2));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn get_feeds_empty() {
|
||||||
|
let adapter = setup_adapter().await;
|
||||||
|
let pool = adapter.get_pool();
|
||||||
|
let user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
let feeds = user.get_feeds(pool).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(feeds.len(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,28 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
Adapter,
|
Adapter,
|
||||||
AdapterBuilder,
|
AdapterBuilder,
|
||||||
|
AdapterPool,
|
||||||
|
db::{
|
||||||
|
Channel,
|
||||||
|
Feed,
|
||||||
|
User,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
use reqwest::Url;
|
||||||
use chrono::{
|
use chrono::{
|
||||||
Utc,
|
Utc,
|
||||||
TimeZone,
|
TimeZone,
|
||||||
DateTime
|
DateTime
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const FEED1: &str = "https://example.com/feed";
|
||||||
|
pub const FEED2: &str = "https://example2.com/feed";
|
||||||
pub const USERNAME: &str = "Alice";
|
pub const USERNAME: &str = "Alice";
|
||||||
pub const USERNAME2: &str = "Bob";
|
pub const USERNAME2: &str = "Bob";
|
||||||
|
pub const FEED_TITLE: &str = "My Feed!";
|
||||||
|
pub const FEED_TITLE2: &str = "My Second Feed!";
|
||||||
|
pub const CHANNEL_TITLE: &str = "My Channel!";
|
||||||
|
pub const CHANNEL_DESC: &str = "My Channel's description";
|
||||||
|
|
||||||
pub fn get_datetime() -> DateTime<Utc> {
|
pub fn get_datetime() -> DateTime<Utc> {
|
||||||
Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap()
|
Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap()
|
||||||
|
|
@ -22,3 +35,13 @@ pub async fn setup_adapter() -> Adapter {
|
||||||
.database_url("sqlite::memory:")
|
.database_url("sqlite::memory:")
|
||||||
.create().await.unwrap()
|
.create().await.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn setup_channel(pool: &AdapterPool) -> Channel {
|
||||||
|
let url = Url::parse(FEED1).unwrap();
|
||||||
|
Channel::get_or_create(pool, url).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn setup_feed(pool: &AdapterPool) -> Feed {
|
||||||
|
let user = User::create(pool, USERNAME).await.unwrap();
|
||||||
|
Feed::create(pool, user.key(), FEED_TITLE).await.unwrap()
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue