diff --git a/koucha/Cargo.lock b/koucha/Cargo.lock index c9fd8ab..1fb6b65 100644 --- a/koucha/Cargo.lock +++ b/koucha/Cargo.lock @@ -53,9 +53,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" dependencies = [ "aws-lc-sys", "zeroize", @@ -63,9 +63,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" dependencies = [ "cc", "cmake", @@ -73,6 +73,58 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "base64" version = "0.22.1" @@ -117,15 +169,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" dependencies = [ "find-msvc-tools", "jobserver", @@ -452,9 +504,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" [[package]] name = "flume" @@ -726,6 +778,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.8.1" @@ -740,6 +798,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -766,13 +825,14 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.20" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", @@ -791,9 +851,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.65" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -987,9 +1047,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -999,6 +1059,7 @@ dependencies = [ name = "koucha" version = "0.1.0" dependencies = [ + "axum", "chrono", "reqwest", "rss", @@ -1023,9 +1084,9 @@ checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libm" -version = "0.2.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" @@ -1076,6 +1137,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + [[package]] name = "md-5" version = "0.10.6" @@ -1169,9 +1236,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "openssl-probe" -version = "0.2.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" [[package]] name = "parking" @@ -1276,9 +1343,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] @@ -1307,7 +1374,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -1329,7 +1396,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -1351,9 +1418,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -1559,9 +1626,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "web-time", "zeroize", @@ -1596,9 +1663,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "aws-lc-rs", "ring", @@ -1708,6 +1775,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1770,9 +1848,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.12" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" @@ -1785,9 +1863,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", @@ -1852,7 +1930,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -1934,7 +2012,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -1971,7 +2049,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", "whoami", ] @@ -1995,7 +2073,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.17", "tracing", "url", ] @@ -2062,9 +2140,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.7.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -2092,11 +2170,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.17", ] [[package]] @@ -2112,9 +2190,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -2221,6 +2299,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2387,9 +2466,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] @@ -2402,9 +2481,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -2415,12 +2494,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", - "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -2429,9 +2507,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2439,9 +2517,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2", @@ -2452,18 +2530,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -2481,9 +2559,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -2867,9 +2945,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -2902,18 +2980,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", @@ -2982,6 +3060,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" diff --git a/koucha/Cargo.toml b/koucha/Cargo.toml index 1106901..186198f 100644 --- a/koucha/Cargo.toml +++ b/koucha/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] +axum = "0.8.8" reqwest = "0.13.1" rss = "2.0.12" tokio = { version = "1.49.0", features = ["full"] } diff --git a/koucha/src/bin/server.rs b/koucha/src/bin/server.rs index e69de29..5ca7b99 100644 --- a/koucha/src/bin/server.rs +++ b/koucha/src/bin/server.rs @@ -0,0 +1,7 @@ +use std::error::Error; + +#[tokio::main] +async fn main() -> Result<(), Box> { + Ok(()) +} + diff --git a/koucha/src/db.rs b/koucha/src/db.rs index 9be8130..cea05ac 100644 --- a/koucha/src/db.rs +++ b/koucha/src/db.rs @@ -4,6 +4,8 @@ mod feed; pub use feed::Feed; mod feed_channel; pub use feed_channel::FeedChannel; +mod feed_item; +pub use feed_item::FeedItem; mod channel; pub use channel::Channel; mod item; @@ -15,16 +17,17 @@ macro_rules! define_key { pub struct $name(i64); }; - ($name:ident, $($field:ident : $type:ty),* $(,)?) => { + ($name:ident, $($field:ident),* $(,)?) => { #[derive(PartialEq, Debug, Copy, Clone)] pub struct $name { - $($field: $type),* + $($field: i64),* } }; } define_key!(UserKey); define_key!(FeedKey); -define_key!(FeedChannelKey, feed_key: FeedKey, channel_key: ChannelKey); +define_key!(FeedChannelKey, feed_id, channel_id); +define_key!(FeedItemKey, feed_id, item_id); define_key!(ChannelKey); define_key!(ItemKey); diff --git a/koucha/src/db/channel.rs b/koucha/src/db/channel.rs index eaa03be..b79454f 100644 --- a/koucha/src/db/channel.rs +++ b/koucha/src/db/channel.rs @@ -10,6 +10,7 @@ use crate::{ feed_channel::UnparsedFeedChannel, item::UnparsedItem, }, + fetch::FetchedRSSChannel, }; pub struct UnparsedChannel { @@ -22,7 +23,7 @@ pub struct UnparsedChannel { impl UnparsedChannel { pub fn parse(self) -> Result { Ok(Channel { - key: ChannelKey(self.id), + id: ChannelKey(self.id), title: self.title, link: Url::parse(&self.link)?, description: self.description, @@ -35,7 +36,7 @@ impl UnparsedChannel { } pub struct Channel { - key: ChannelKey, + id: ChannelKey, title: String, link: Url, description: Option, @@ -43,27 +44,31 @@ pub struct Channel { } impl Channel { - pub fn key(&self) -> ChannelKey { self.key } + pub fn id(&self) -> ChannelKey { self.id } 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> { self.last_fetched } pub async fn get_all(pool: &AdapterPool) -> Result> { - sqlx::query_as!( + let channels: Result> = sqlx::query_as!( UnparsedChannel, "SELECT id, title, link, description, last_fetched FROM channels" - ).fetch_all(&pool.0).await?.into_iter().map(UnparsedChannel::parse).collect() + ).fetch_all(&pool.0).await?.into_iter().map(UnparsedChannel::parse).collect(); + + channels } - pub async fn get(pool: &AdapterPool, key: ChannelKey) -> Result { - sqlx::query_as!( + pub async fn get(pool: &AdapterPool, id: ChannelKey) -> Result { + let channel: Result = sqlx::query_as!( UnparsedChannel, "SELECT id, title, link, description, last_fetched FROM channels WHERE id = ?", - key.0 - ).fetch_one(&pool.0).await?.parse() + id.0 + ).fetch_one(&pool.0).await?.parse(); + + channel } pub async fn get_or_create( @@ -71,38 +76,84 @@ impl Channel { ) -> Result { let link_str = link.as_str(); - sqlx::query_as!( + let channel = 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() + ).fetch_one(&pool.0).await?.parse(); + + channel + } + + // TODO implement fetch skipping + pub fn should_skip_fetch(&self) -> bool { false } + + pub async fn update_metadata( + &self, pool: &AdapterPool, fetched: FetchedRSSChannel + ) -> Result<()> { + let title = fetched.title(); + let description = fetched.description(); + let link = fetched.link().as_str(); + let fetched_at = fetched.fetched_at().to_rfc2822(); + sqlx::query!( + "UPDATE channels + SET title = ?, link = ?, description = ?, + last_fetched = ? + WHERE id = ?", + title, link, description, fetched_at, + self.id.0 + ).execute(&pool.0).await?; + + Ok(()) } async fn get_feed_channels( &self, pool: &AdapterPool ) -> Result> { - sqlx::query_as!( + let feeds: Result> = sqlx::query_as!( UnparsedFeedChannel, "SELECT channel_id, feed_id, initial_score, gravity, boost FROM feed_channels WHERE channel_id = ?", - self.key.0 + self.id.0 ).fetch_all(&pool.0).await?.into_iter() - .map(UnparsedFeedChannel::parse).collect() + .map(UnparsedFeedChannel::parse).collect(); + + feeds + } + + pub async fn update_items( + &self, pool: &AdapterPool, fetched: FetchedRSSChannel + ) -> Result<()> { + let fetched_at = fetched.fetched_at(); + + let feed_channels = self.get_feed_channels(pool).await?; + + for rss_item in fetched.items() { + let new_item = Item::get_or_create(pool, self.id, rss_item.guid()).await?; + new_item.update_content(pool, rss_item, &fetched_at).await?; + for feed_channel in &feed_channels { + feed_channel.add_item(pool, &new_item).await?; + } + } + + Ok(()) } pub async fn get_items(&self, pool: &AdapterPool) -> Result> { - sqlx::query_as!( + let items: Result> = sqlx::query_as!( UnparsedItem, "SELECT id as `id!`, channel_id, fetched_at, title, description, content FROM items WHERE channel_id = ?", - self.key.0 - ).fetch_all(&pool.0).await?.into_iter().map(UnparsedItem::parse).collect() + self.id.0 + ).fetch_all(&pool.0).await?.into_iter().map(UnparsedItem::parse).collect(); + + items } } @@ -110,7 +161,10 @@ impl Channel { mod tests { use super::*; use crate::{ - db::{Feed, User}, + db::{ + Feed, + User, + }, test_utils::{ FEED1, FEED2, CHANNEL_TITLE, CHANNEL_DESC, USERNAME, FEED_TITLE, FEED_TITLE2, ITEM_GUID, ITEM_GUID2, @@ -119,7 +173,7 @@ mod tests { }, }; use chrono::TimeZone; - + #[test] fn parse_unparsed_item() { const CHANNEL_ID: i64 = 1; @@ -134,7 +188,7 @@ mod tests { }; let channel = raw_channel.parse().unwrap(); - assert_eq!(channel.key.0, CHANNEL_ID); + assert_eq!(channel.id.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())); @@ -159,10 +213,10 @@ mod tests { 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.id()).await.unwrap(); - let channel_b = Channel::get(pool, channel_a.key()).await.unwrap(); - - assert_eq!(channel_a.key, channel_b.key); + assert_eq!(channel_a.id, channel_b.id); 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); @@ -172,12 +226,12 @@ mod tests { #[tokio::test] async fn create() { let adapter = setup_adapter().await; - let pool = adapter.get_pool(); + 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!(channel.id().0 > 0); assert_eq!(channel.link().as_str(), FEED1); assert!(channel.title().len() > 0); } @@ -185,19 +239,19 @@ mod tests { #[tokio::test] async fn create_duplicate_returns_existing() { let adapter = setup_adapter().await; - let pool = adapter.get_pool(); + 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()); + assert_eq!(channel1.id(), channel2.id()); } #[tokio::test] async fn get_all_channels() { let adapter = setup_adapter().await; - let pool = adapter.get_pool(); + let pool = adapter.get_pool(); let url_feed1 = Url::parse(FEED1).unwrap(); let url_feed2 = Url::parse(FEED2).unwrap(); @@ -216,11 +270,29 @@ mod tests { let channel = setup_channel(pool).await; let user = User::create(pool, USERNAME).await.unwrap(); - let feed1 = Feed::create(pool, user.key(), FEED_TITLE).await.unwrap(); - let feed2 = Feed::create(pool, user.key(), FEED_TITLE2).await.unwrap(); + let feed1 = Feed::create(pool, user.id(), FEED_TITLE).await.unwrap(); + let feed2 = Feed::create(pool, user.id(), FEED_TITLE2).await.unwrap(); - feed1.add_channel(pool, channel.key).await.unwrap(); - feed2.add_channel(pool, channel.key).await.unwrap(); + feed1.add_channel(pool, channel.id).await.unwrap(); + feed2.add_channel(pool, channel.id).await.unwrap(); + + let fc_list = channel.get_feed_channels(pool).await.unwrap(); + + assert_eq!(fc_list.len(), 2); + } + + #[tokio::test] + async fn get_channels() { + let adapter = setup_adapter().await; + let pool = adapter.get_pool(); + let channel = setup_channel(pool).await; + + let user = User::create(pool, USERNAME).await.unwrap(); + let feed1 = Feed::create(pool, user.id(), FEED_TITLE).await.unwrap(); + let feed2 = Feed::create(pool, user.id(), FEED_TITLE2).await.unwrap(); + + feed1.add_channel(pool, channel.id).await.unwrap(); + feed2.add_channel(pool, channel.id).await.unwrap(); let fc_list = channel.get_feed_channels(pool).await.unwrap(); @@ -233,8 +305,8 @@ mod tests { let pool = adapter.get_pool(); let channel = setup_channel(pool).await; - Item::get_or_create(pool, channel.key(), ITEM_GUID).await.unwrap(); - Item::get_or_create(pool, channel.key(), ITEM_GUID2).await.unwrap(); + Item::get_or_create(pool, channel.id(), ITEM_GUID).await.unwrap(); + Item::get_or_create(pool, channel.id(), ITEM_GUID2).await.unwrap(); let items = channel.get_items(pool).await.unwrap(); diff --git a/koucha/src/db/feed.rs b/koucha/src/db/feed.rs index a162190..c3c95d1 100644 --- a/koucha/src/db/feed.rs +++ b/koucha/src/db/feed.rs @@ -19,61 +19,65 @@ pub struct UnparsedFeed { impl UnparsedFeed { pub fn parse(self) -> Result { Ok(Feed { - key: FeedKey(self.id), + id: FeedKey(self.id), title: self.title, }) } } pub struct Feed { - key: FeedKey, + id: FeedKey, title: String, } impl Feed { - pub fn key(&self) -> FeedKey { self.key } + pub fn id(&self) -> FeedKey { self.id } pub fn title(&self) -> &str { &self.title } pub async fn get( - pool: &AdapterPool, key: FeedKey + pool: &AdapterPool, id: FeedKey ) -> Result { - sqlx::query_as!( + let feed = sqlx::query_as!( UnparsedFeed, "SELECT id, title FROM feeds WHERE id = ?", - key.0 - ).fetch_one(&pool.0).await?.parse() + id.0 + ).fetch_one(&pool.0).await?.parse(); + + feed } pub async fn create( - pool: &AdapterPool, user_key: UserKey, title: &str + pool: &AdapterPool, user_id: UserKey, title: &str ) -> Result { - sqlx::query_as!( + let new_feed = 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() + user_id.0, title + ).fetch_one(&pool.0).await?.parse(); + + new_feed } pub async fn update_title( - pool: &AdapterPool, key: FeedKey, new_title: &str + pool: &AdapterPool, id: FeedKey, new_title: &str ) -> Result<()> { sqlx::query!( "UPDATE feeds SET title = ? WHERE id = ?", - new_title, key.0 + new_title, id.0 ).execute(&pool.0).await?; Ok(()) } pub async fn add_channel( - &self, pool: &AdapterPool, channel_key: ChannelKey + &self, pool: &AdapterPool, channel_id: ChannelKey ) -> Result<()> { sqlx::query!( "INSERT INTO feed_channels (feed_id, channel_id) VALUES (?, ?)", - self.key.0, channel_key.0 + self.id.0, channel_id.0 ).execute(&pool.0).await?; Ok(()) @@ -82,7 +86,7 @@ impl Feed { pub async fn get_items( &self, pool: &AdapterPool, limit: u8, offset: u32 ) -> Result> { - sqlx::query_as!( + let items: Result> = sqlx::query_as!( UnparsedItem, "SELECT i.id as `id!`, i.channel_id, i.fetched_at, i.title, i.description, i.content @@ -91,22 +95,26 @@ impl Feed { WHERE feed_id = ? AND archived = FALSE ORDER BY score DESC LIMIT ? OFFSET ?", - self.key.0, limit, offset - ).fetch_all(&pool.0).await?.into_iter().map(UnparsedItem::parse).collect() + 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> { - sqlx::query_as!( + 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.key.0 + self.id.0 ).fetch_all(&pool.0).await?.into_iter() - .map(UnparsedChannel::parse).collect() + .map(UnparsedChannel::parse).collect(); + + channels } } @@ -134,7 +142,7 @@ mod tests { let f = uf.parse().unwrap(); - assert_eq!(f.key.0, FID); + assert_eq!(f.id.0, FID); assert_eq!(f.title, FEED_TITLE); } @@ -144,9 +152,9 @@ mod tests { let pool = adapter.get_pool(); let feed = setup_feed(pool).await; - let gotten_feed = Feed::get(pool, feed.key).await.unwrap(); + let gotten_feed = Feed::get(pool, feed.id).await.unwrap(); - assert_eq!(feed.key, gotten_feed.key); + assert_eq!(feed.id, gotten_feed.id); assert_eq!(feed.title, gotten_feed.title); } @@ -155,9 +163,9 @@ mod tests { 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(); + let feed = Feed::create(pool, user.id(), FEED_TITLE).await.unwrap(); - assert!(feed.key().0 > 0); + assert!(feed.id().0 > 0); assert_eq!(feed.title(), FEED_TITLE); } @@ -168,9 +176,9 @@ mod tests { let pool = adapter.get_pool(); let feed = setup_feed(pool).await; - Feed::update_title(pool, feed.key(), NEW_FEED_TITLE).await.unwrap(); + Feed::update_title(pool, feed.id(), NEW_FEED_TITLE).await.unwrap(); - let updated = Feed::get(pool, feed.key()).await.unwrap(); + let updated = Feed::get(pool, feed.id()).await.unwrap(); assert_eq!(updated.title(), NEW_FEED_TITLE); } @@ -181,12 +189,12 @@ mod tests { let feed = setup_feed(pool).await; let channel = setup_channel(pool).await; - feed.add_channel(pool, channel.key()).await.unwrap(); + 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.key().0, channel.key().0); + assert_eq!(gotten_channel.id().0, channel.id().0); } #[tokio::test] @@ -199,8 +207,8 @@ mod tests { 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(); + feed.add_channel(pool, channel1.id()).await.unwrap(); + feed.add_channel(pool, channel2.id()).await.unwrap(); let channels = feed.get_channels(pool).await.unwrap(); diff --git a/koucha/src/db/feed_channel.rs b/koucha/src/db/feed_channel.rs index e2df61a..d736c2d 100644 --- a/koucha/src/db/feed_channel.rs +++ b/koucha/src/db/feed_channel.rs @@ -6,7 +6,6 @@ use crate::{ ChannelKey, Feed, FeedKey, - FeedChannelKey, Item, }, score::{ @@ -27,10 +26,8 @@ pub struct UnparsedFeedChannel { impl UnparsedFeedChannel { pub fn parse(self) -> Result { Ok(FeedChannel { - key: FeedChannelKey { - feed_key: FeedKey(self.feed_id), - channel_key: ChannelKey(self.channel_id), - }, + channel_id: ChannelKey(self.channel_id), + feed_id: FeedKey(self.feed_id), initial_score: Score::new(self.initial_score), gravity: Gravity::new(self.gravity), boost: Boost::new(self.boost), @@ -39,7 +36,8 @@ impl UnparsedFeedChannel { } pub struct FeedChannel { - key: FeedChannelKey, + channel_id: ChannelKey, + feed_id: FeedKey, initial_score: Score, gravity: Gravity, boost: Boost, @@ -47,10 +45,10 @@ pub struct FeedChannel { impl FeedChannel { pub async fn get_channel(&self, pool: &AdapterPool) -> Result { - Channel::get(pool, self.key.channel_key).await + Channel::get(pool, self.channel_id).await } pub async fn get_feed(&self, pool: &AdapterPool) -> Result { - Feed::get(pool, self.key.feed_key).await + Feed::get(pool, self.feed_id).await } pub async fn add_item( @@ -62,14 +60,14 @@ impl FeedChannel { async fn add_item_at( &self, pool: &AdapterPool, item: &Item, add_at: DateTime ) -> Result<()> { - let int_item_id = item.key().0; + 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.key.feed_key.0, int_item_id, int_initial_score, string_last_updated + self.feed_id.0, int_item_id, int_initial_score, string_last_updated ).execute(&pool.0).await?; Ok(()) } @@ -107,8 +105,8 @@ mod tests { let fc = ufc.parse().unwrap(); - assert_eq!(fc.key.channel_key.0, CID); - assert_eq!(fc.key.feed_key.0, FID); + 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); @@ -124,17 +122,15 @@ mod tests { let channel = Channel::get_or_create(pool, url).await.unwrap(); let fc = FeedChannel { - key: FeedChannelKey { - feed_key: FeedKey(1), // Fake Feed - channel_key: channel.key(), - }, + channel_id: channel.id(), + feed_id: FeedKey(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.key(), channel.key()); + assert_eq!(channel_from_fc.id(), channel.id()); } #[tokio::test] @@ -143,20 +139,18 @@ mod tests { let pool = adapter.get_pool(); let user = User::create(pool, "Alice").await.unwrap(); - let feed = Feed::create(pool, user.key(), "My Feed").await.unwrap(); + let feed = Feed::create(pool, user.id(), "My Feed").await.unwrap(); let fc = FeedChannel { - key: FeedChannelKey { - feed_key: feed.key(), - channel_key: ChannelKey(1), // Fake Channel - }, + channel_id: ChannelKey(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.key(), feed.key()); + assert_eq!(feed_from_fc.id(), feed.id()); } #[tokio::test] @@ -166,23 +160,21 @@ mod tests { let pool = adapter.get_pool(); let user = User::create(pool, "Alice").await.unwrap(); - let feed = Feed::create(pool, user.key(), "My Feed").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 { - key: FeedChannelKey { - feed_key: feed.key(), - channel_key: channel.key(), - }, + 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.key(), "item-guid").await.unwrap(); + 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].key(), item.key()); + assert_eq!(items[0].id(), item.id()); } } diff --git a/koucha/src/db/feed_item.rs b/koucha/src/db/feed_item.rs new file mode 100644 index 0000000..8c96b8a --- /dev/null +++ b/koucha/src/db/feed_item.rs @@ -0,0 +1,101 @@ +use chrono::{DateTime, Utc}; +use crate::{ + Result, + AdapterPool, + db::{ + FeedItemKey, + }, + score::{ + TimedScore, + UnparsedTimedScore + }, +}; + +pub struct UnparsedFeedItem { + pub item_id: i64, + pub feed_id: i64, + pub score: i64, + pub last_updated: String, + pub boosted_at: Option, +} +impl UnparsedFeedItem { + pub fn parse(self) -> Result { + Ok(FeedItem { + key: FeedItemKey { + feed_id: self.feed_id, + item_id: self.item_id, + }, + score: (UnparsedTimedScore { + value: self.score, + last_updated: DateTime::parse_from_rfc2822(&self.last_updated)? + .with_timezone(&Utc), + last_boosted: self.boosted_at.as_deref() + .map(DateTime::parse_from_rfc2822) + .transpose()? + .map(|dt| dt.with_timezone(&Utc)), + }).parse(), + }) + } +} + +pub struct FeedItem { + key: FeedItemKey, + score: TimedScore, +} + +impl FeedItem { + pub fn key(&self) -> FeedItemKey { self.key } + pub fn score(&self) -> TimedScore { self.score.clone() } + + pub async fn archive( + &self, pool: &AdapterPool + ) -> Result<()> { + sqlx::query!( + "UPDATE feed_items + SET archived = ? + WHERE feed_id = ? AND item_id = ?", + true, self.key.feed_id, self.key.item_id + ).execute(&pool.0).await?; + + Ok(()) + } + + pub async fn update_score( + pool: &AdapterPool, key: FeedItemKey, new_score: TimedScore + ) -> Result<()> { + let unparsed_score = UnparsedTimedScore::unparse(new_score); + let last_updated = unparsed_score.last_updated.to_rfc2822(); + let boosted_at = unparsed_score.last_boosted.map(|lb| lb.to_rfc2822()); + + sqlx::query!( + "UPDATE feed_items + SET score = ?, last_updated = ?, boosted_at = ? + WHERE feed_id = ? AND item_id = ?", + unparsed_score.value, last_updated, boosted_at, key.feed_id, key.item_id + ).execute(&pool.0).await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::get_datetime; + + #[test] + fn parse_feed_item() { + let dt = get_datetime(); + let upi = UnparsedFeedItem { + item_id: 1, + feed_id: 2, + score: 5, + last_updated: dt.to_string(), + boosted_at: None, + }; + + let fi = upi.parse().unwrap(); + assert_eq!(fi.key.item_id, 1); + assert_eq!(fi.key.feed_id, 2); + } +} diff --git a/koucha/src/db/item.rs b/koucha/src/db/item.rs index 31ee857..e3486d4 100644 --- a/koucha/src/db/item.rs +++ b/koucha/src/db/item.rs @@ -5,6 +5,7 @@ use crate::{ ChannelKey, ItemKey, }, + fetch::FetchedRSSItem, }; use chrono::{DateTime, Utc}; @@ -21,7 +22,7 @@ pub struct UnparsedItem { impl UnparsedItem { pub fn parse(self) -> Result { Ok(Item { - key: ItemKey(self.id), + id: ItemKey(self.id), channel_id: ChannelKey(self.channel_id), fetched_at: match self.fetched_at { Some(dt_str) => Some(DateTime::parse_from_rfc2822(&dt_str)? @@ -37,9 +38,10 @@ impl UnparsedItem { } pub struct Item { - key: ItemKey, + id: ItemKey, channel_id: ChannelKey, + #[allow(dead_code)] // TODO: Use for score decay calculations later fetched_at: Option>, title: Option, description: Option, @@ -47,7 +49,7 @@ pub struct Item { } impl Item { - pub fn key(&self) -> ItemKey { self.key } + pub fn id(&self) -> ItemKey { self.id } pub fn channel(&self) -> ChannelKey { self.channel_id } pub fn title(&self) -> Option<&str> { self.title.as_deref() } pub fn description(&self) -> Option<&str> { self.description.as_deref() } @@ -69,6 +71,26 @@ impl Item { item } + + pub async fn update_content( + &self, pool: &AdapterPool, fetched: &FetchedRSSItem, fetched_at: &DateTime + ) -> Result<()> { + let title = fetched.title(); + let description = fetched.description(); + let content = fetched.content(); + let string_fetched_at = fetched_at.to_rfc2822(); + + sqlx::query!( + "UPDATE items + SET title = ?, description = ?, content = ?, + fetched_at = ? + WHERE id = ?", + title, description, content, string_fetched_at, + self.id.0 + ).execute(&pool.0).await?; + + Ok(()) + } } #[cfg(test)] @@ -98,7 +120,7 @@ mod tests { }; let item = raw_item.parse().unwrap(); - assert_eq!(item.key.0, ITEM_ID); + assert_eq!(item.id.0, ITEM_ID); assert_eq!(item.channel_id.0, CHANNEL_ID); assert_eq!(item.fetched_at, Some(date)); assert_eq!(item.title, Some(ITEM_TITLE.to_string())); @@ -114,9 +136,9 @@ mod tests { let pool = adapter.get_pool(); let channel = setup_channel(pool).await; - let item1 = Item::get_or_create(pool, channel.key(), ITEM_GUID).await.unwrap(); - let item2 = Item::get_or_create(pool, channel.key(), ITEM_GUID).await.unwrap(); + let item1 = Item::get_or_create(pool, channel.id(), ITEM_GUID).await.unwrap(); + let item2 = Item::get_or_create(pool, channel.id(), ITEM_GUID).await.unwrap(); - assert_eq!(item1.key(), item2.key()); + assert_eq!(item1.id(), item2.id()); } } diff --git a/koucha/src/db/user.rs b/koucha/src/db/user.rs index 038b82a..ef7ba51 100644 --- a/koucha/src/db/user.rs +++ b/koucha/src/db/user.rs @@ -15,52 +15,60 @@ pub struct UnparsedUser { impl UnparsedUser { pub fn parse(self) -> Result { Ok(User { - key: UserKey(self.id), + id: UserKey(self.id), name: self.name }) } } pub struct User { - key: UserKey, + id: UserKey, name: String, } impl User { - pub fn key(&self) -> UserKey { self.key } + pub fn id(&self) -> UserKey { self.id } pub fn name(&self) -> &str { &self.name } - pub async fn get(pool: &AdapterPool, key: UserKey) -> Result { - sqlx::query_as!( + pub async fn get(pool: &AdapterPool, id: UserKey) -> Result { + let user = sqlx::query_as!( UnparsedUser, "SELECT id, name FROM users WHERE id = ?", - key.0 - ).fetch_one(&pool.0).await?.parse() + id.0 + ).fetch_one(&pool.0).await?.parse(); + + user } pub async fn get_all(pool: &AdapterPool) -> Result> { - sqlx::query_as!( + let users: Result> = sqlx::query_as!( UnparsedUser, "SELECT id, name FROM users" - ).fetch_all(&pool.0).await?.into_iter().map(UnparsedUser::parse).collect() + ).fetch_all(&pool.0).await?.into_iter().map(UnparsedUser::parse).collect(); + + users } pub async fn create(pool: &AdapterPool, name: &str) -> Result { - sqlx::query_as!( - UnparsedUser, + let result = sqlx::query!( "INSERT INTO users (name) VALUES (?) RETURNING id, name", name - ).fetch_one(&pool.0).await?.parse() + ).fetch_one(&pool.0).await?; + + Ok(Self { + id: UserKey(result.id), + name: result.name, + }) } pub async fn update_name( - pool: &AdapterPool, key: UserKey, new_name: &str + pool: &AdapterPool, id: UserKey, new_name: &str ) -> Result<()> { sqlx::query!( "UPDATE users SET name = ? WHERE id = ?", - new_name, key.0 + new_name, id.0 ).execute(&pool.0).await?; Ok(()) @@ -70,7 +78,7 @@ impl User { let feeds: Result> = sqlx::query_as!( UnparsedFeed, "SELECT id, title FROM feeds WHERE user_id = ?", - self.key.0 + self.id.0 ).fetch_all(&pool.0).await?.into_iter() .map(UnparsedFeed::parse).collect(); @@ -98,7 +106,7 @@ mod tests { }; let user = unparsed_user.parse().unwrap(); - assert_eq!(user.key.0, UID); + assert_eq!(user.id.0, UID); assert_eq!(user.name, USERNAME); } @@ -108,9 +116,9 @@ mod tests { let pool = adapter.get_pool(); let new_user = User::create(pool, USERNAME).await.unwrap(); - let fetched_user = User::get(pool, new_user.key).await.unwrap(); + let fetched_user = User::get(pool, new_user.id).await.unwrap(); assert_eq!(fetched_user.name, USERNAME); - assert_eq!(fetched_user.key.0, 1); + assert_eq!(fetched_user.id.0, 1); } #[tokio::test] @@ -134,7 +142,7 @@ mod tests { let user = User::create(pool, USERNAME).await.unwrap(); assert_eq!(user.name, USERNAME); - assert_eq!(user.key.0, 1); + assert_eq!(user.id.0, 1); } #[tokio::test] @@ -156,9 +164,9 @@ mod tests { let pool = adapter.get_pool(); let user = User::create(pool, USERNAME).await.unwrap(); - User::update_name(pool, user.key, NEW_USERNAME).await.unwrap(); + User::update_name(pool, user.id, NEW_USERNAME).await.unwrap(); - let updated = User::get(pool, user.key).await.unwrap(); + let updated = User::get(pool, user.id).await.unwrap(); assert_eq!(updated.name, NEW_USERNAME); } @@ -169,7 +177,7 @@ mod tests { let user1 = User::create(pool, USERNAME).await.unwrap(); User::create(pool, USERNAME2).await.unwrap(); - let status = User::update_name(pool, user1.key, USERNAME2).await; + let status = User::update_name(pool, user1.id, USERNAME2).await; assert!(status.is_err()); } @@ -179,8 +187,8 @@ mod tests { 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(); + Feed::create(pool, user.id, FEED_TITLE).await.unwrap(); + Feed::create(pool, user.id, FEED_TITLE2).await.unwrap(); let feeds = user.get_feeds(pool).await.unwrap(); assert_eq!(feeds.len(), 2); diff --git a/koucha/src/fetch.rs b/koucha/src/fetch.rs index 53ba2b9..a25b9de 100644 --- a/koucha/src/fetch.rs +++ b/koucha/src/fetch.rs @@ -1,6 +1,6 @@ use crate::{ Result, - db::Channel, + db::{Channel, ChannelId}, AdapterClient, }; use reqwest::Url; @@ -60,6 +60,10 @@ impl FetchedRSSChannel { pub async fn fetch_channel( client: &AdapterClient, channel: Channel ) -> Result> { + if channel.should_skip_fetch() { + return Ok(None); + } + let bytestream = client.0.get(channel.link().clone()) .send().await? .bytes().await?; diff --git a/koucha/src/test_utils.rs b/koucha/src/test_utils.rs index 6a21ca9..870a257 100644 --- a/koucha/src/test_utils.rs +++ b/koucha/src/test_utils.rs @@ -31,6 +31,7 @@ pub const ITEM_TITLE: &str = "My Item!"; pub const ITEM_DESC: &str = "My Item's description"; pub const ITEM_CONT: &str = "The content of my Item"; + pub fn get_datetime() -> DateTime { Utc.with_ymd_and_hms(2020,1,1,0,0,0).unwrap() } @@ -48,5 +49,5 @@ pub async fn setup_channel(pool: &AdapterPool) -> Channel { 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() + Feed::create(pool, user.id(), FEED_TITLE).await.unwrap() }