Skip to content

Commit f871741

Browse files
committed
feat: browse feeds
1 parent e41773f commit f871741

32 files changed

+1080
-183
lines changed

.helix/languages.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ lens.enable = false
2424
hover.actions.enable = false
2525

2626
[language-server.rust-analyzer.config.cargo]
27-
features = []
27+
features = ["auth_static_fut", "introspection"]

Cargo.lock

+5-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

justfile

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
set shell := ["nu", "-c"]
22
kvsd_user := "synduser"
3+
github_pat := env_var_or_default("GH_PAT", "")
34

45
# List recipe
56
default:
@@ -16,9 +17,10 @@ fmt: fmt-toml
1617
fmt-toml:
1718
taplo fmt **.toml
1819

20+
1921
update-gql-schema:
20-
graphql-client introspect-schema http://localhost:5959/graphql \
21-
--header 'authorization: me' out> syndterm/gql/schema.json
22+
@graphql-client introspect-schema http://localhost:5959/graphql \
23+
--header 'authorization: github {{github_pat}}' out> syndterm/gql/schema.json
2224

2325

2426
gen-gql:
@@ -48,7 +50,7 @@ kvsd:
4850

4951
# Run api
5052
api:
51-
cd syndapi; RUST_LOG="syndapi=info,synd::feed::cache=debug,info" cargo run -- \
53+
cd syndapi; RUST_LOG="syndapi=info,synd::feed::cache=debug,info" cargo run --features "introspection" -- \
5254
--kvsd-host 127.0.0.1 --kvsd-port 7379 --kvsd-username {{kvsd_user}} --kvsd-password secret
5355

5456
# Run term

synd/src/types.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ impl Entry {
5252
self.0.summary.as_ref().map(|text| text.content.as_str())
5353
}
5454

55+
pub fn website_url(&self, feed_type: FeedType) -> Option<&str> {
56+
link::find_website_url(feed_type, &self.0.links)
57+
}
58+
5559
/// Return approximate entry bytes size
5660
pub fn approximate_size(&self) -> usize {
5761
let content_size = self
@@ -119,6 +123,18 @@ impl FeedMeta {
119123
}
120124
}
121125

126+
impl<'a> From<&'a FeedMeta> for Cow<'a, FeedMeta> {
127+
fn from(value: &'a FeedMeta) -> Self {
128+
Cow::Borrowed(value)
129+
}
130+
}
131+
132+
impl From<FeedMeta> for Cow<'static, FeedMeta> {
133+
fn from(value: FeedMeta) -> Self {
134+
Cow::Owned(value)
135+
}
136+
}
137+
122138
#[derive(Debug, Clone)]
123139
pub struct Feed {
124140
meta: FeedMeta,
@@ -146,7 +162,7 @@ impl Feed {
146162

147163
impl From<(FeedUrl, feed_rs::model::Feed)> for Feed {
148164
fn from((url, mut feed): (FeedUrl, feedrs::Feed)) -> Self {
149-
let entries = std::mem::replace(&mut feed.entries, Vec::new());
165+
let entries = std::mem::take(&mut feed.entries);
150166
let entries = entries.into_iter().map(Entry).collect();
151167

152168
let meta = FeedMeta { url, feed };

syndapi/Cargo.toml

+12-6
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,31 @@ feed-rs = { workspace = true }
1919
futures-util = { workspace = true }
2020
graphql_client = { workspace = true }
2121
kvsd = { version = "0.1.2" }
22-
moka = { workspace = true, features = ["future"]}
22+
moka = { workspace = true, features = ["future"] }
2323
reqwest = { workspace = true }
2424
serde = { workspace = true }
2525
serde_json = "1.0.111"
26-
supports-color = { version = "2.1.0"}
26+
supports-color = { version = "2.1.0" }
2727
synd = { path = "../synd" }
2828
thiserror = "1.0.56"
2929
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
3030
tracing = { workspace = true }
3131
tracing-subscriber = { workspace = true }
3232
tower = { version = "0.4.13", default_features = false, features = [
33-
"limit","timeout"
33+
"limit",
34+
"timeout",
3435

3536
] }
3637
tower-http = { version = "0.5.1", default_features = false, features = [
37-
"trace","sensitive-headers","cors","limit"
38+
"trace",
39+
"sensitive-headers",
40+
"cors",
41+
"limit",
3842
] }
43+
pin-project = "1.1.4"
3944

40-
# handson
4145
[features]
4246

43-
foo = []
47+
# Enable graphql introspection
48+
introspection = []
49+
auth_static_fut = []

syndapi/src/gql/mod.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod query;
22
pub use query::Query;
33

44
mod mutation;
5-
use async_graphql::{EmptySubscription, Schema};
5+
use async_graphql::{extensions::Tracing, EmptySubscription, Schema, SchemaBuilder};
66
pub use mutation::Mutation;
77

88
use crate::{principal::Principal, usecase};
@@ -37,6 +37,15 @@ pub mod handler {
3737
}
3838
}
3939

40+
pub fn schema_builder() -> SchemaBuilder<Query, Mutation, EmptySubscription> {
41+
#[cfg(feature = "introspection")]
42+
let schema = Schema::build(Query, Mutation, EmptySubscription);
43+
#[cfg(not(feature = "introspection"))]
44+
let schema = Schema::build(Query, Mutation, EmptySubscription).disable_introspection();
45+
46+
schema.extension(Tracing)
47+
}
48+
4049
impl<'a> usecase::Context for &async_graphql::Context<'a> {
4150
fn principal(&self) -> Principal {
4251
self.data_unchecked::<Principal>().clone()

syndapi/src/gql/object/mod.rs

+53-25
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{borrow::Cow, sync::Arc};
22

33
use async_graphql::{
4-
connection::{Connection, Edge},
4+
connection::{Connection, ConnectionNameType, Edge, EdgeNameType, EmptyFields},
55
Enum, Object, SimpleObject, ID,
66
};
77
use feed_rs::model as feedrs;
@@ -34,29 +34,44 @@ impl From<feedrs::Link> for Link {
3434
}
3535
}
3636

37-
pub struct Entry(types::Entry);
37+
pub struct Entry<'a> {
38+
meta: Cow<'a, types::FeedMeta>,
39+
entry: types::Entry,
40+
}
3841

3942
#[Object]
40-
impl Entry {
43+
impl<'a> Entry<'a> {
44+
/// Feed of this entry
45+
async fn feed(&'a self) -> FeedMeta<'a> {
46+
self.meta.as_ref().into()
47+
}
4148
/// Entry title
4249
async fn title(&self) -> Option<&str> {
43-
self.0.title()
50+
self.entry.title()
4451
}
4552

4653
/// The time at which the entry published
4754
async fn published(&self) -> Option<scalar::Rfc3339Time> {
48-
self.0.published().map(Into::into)
55+
self.entry.published().map(Into::into)
4956
}
5057

5158
/// Entry summary
5259
async fn summary(&self) -> Option<&str> {
53-
self.0.summary()
60+
self.entry.summary()
61+
}
62+
63+
/// Link to websiteurl at which this entry is published
64+
async fn website_url(&self) -> Option<&str> {
65+
self.entry.website_url(self.meta.r#type())
5466
}
5567
}
5668

57-
impl From<types::Entry> for Entry {
58-
fn from(value: types::Entry) -> Self {
59-
Self(value)
69+
impl<'a> Entry<'a> {
70+
pub fn new(meta: impl Into<Cow<'a, types::FeedMeta>>, entry: types::Entry) -> Self {
71+
Self {
72+
meta: meta.into(),
73+
entry,
74+
}
6075
}
6176
}
6277

@@ -103,12 +118,20 @@ impl Feed {
103118
async fn entries(
104119
&self,
105120
#[graphql(default = 5)] first: Option<i32>,
106-
) -> Connection<usize, Entry> {
121+
) -> Connection<
122+
usize,
123+
Entry,
124+
EmptyFields,
125+
EmptyFields,
126+
FeedEntryConnectionName,
127+
FeedEntryEdgeName,
128+
> {
107129
let first = first.unwrap_or(5).max(0) as usize;
130+
let meta = self.0.meta();
108131
let entries = self
109132
.0
110133
.entries()
111-
.map(|entry| Entry(entry.clone()))
134+
.map(|entry| Entry::new(meta, entry.clone()))
112135
.take(first)
113136
.collect::<Vec<_>>();
114137

@@ -161,6 +184,22 @@ impl Feed {
161184
}
162185
}
163186

187+
pub struct FeedEntryConnectionName;
188+
189+
impl ConnectionNameType for FeedEntryConnectionName {
190+
fn type_name<T: async_graphql::OutputType>() -> String {
191+
"FeedEntryConnection".into()
192+
}
193+
}
194+
195+
pub struct FeedEntryEdgeName;
196+
197+
impl EdgeNameType for FeedEntryEdgeName {
198+
fn type_name<T: async_graphql::OutputType>() -> String {
199+
"FeedEntryEdge".into()
200+
}
201+
}
202+
164203
impl From<Arc<types::Feed>> for Feed {
165204
fn from(value: Arc<types::Feed>) -> Self {
166205
Self(value)
@@ -182,19 +221,8 @@ impl From<types::FeedMeta> for FeedMeta<'static> {
182221
}
183222
}
184223

185-
/// Entry with feed metadata
186-
pub(super) struct FeedEntry<'a> {
187-
pub feed: FeedMeta<'a>,
188-
pub entry: Entry,
189-
}
190-
191-
#[Object]
192-
impl<'a> FeedEntry<'a> {
193-
async fn feed(&self) -> &FeedMeta<'a> {
194-
&self.feed
195-
}
196-
197-
async fn entry(&self) -> &Entry {
198-
&self.entry
224+
impl<'a> From<&'a types::FeedMeta> for FeedMeta<'a> {
225+
fn from(value: &'a types::FeedMeta) -> Self {
226+
Self(Cow::Borrowed(value))
199227
}
200228
}

syndapi/src/gql/query.rs

+17-25
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use async_graphql::{
55

66
use crate::{
77
gql::{
8-
object::{self, id, FeedEntry},
8+
object::{self, id, Entry},
99
run_usecase,
1010
},
1111
usecase::{
@@ -18,6 +18,7 @@ struct Subscription;
1818

1919
#[Object]
2020
impl Subscription {
21+
/// Return Subscribed feeds
2122
async fn feeds(
2223
&self,
2324
cx: &Context<'_>,
@@ -39,6 +40,7 @@ impl Subscription {
3940

4041
let edges = feeds
4142
.into_iter()
43+
.take(first)
4244
.map(|feed| (feed.meta().url().to_owned(), feed))
4345
.map(|(cursor, feed)| (cursor, object::Feed::from(feed)))
4446
.map(|(cursor, feed)| Edge::new(cursor, feed));
@@ -47,19 +49,14 @@ impl Subscription {
4749

4850
Ok(connection)
4951
}
50-
}
51-
52-
struct Feeds;
5352

54-
#[Object]
55-
impl Feeds {
56-
/// Return Latest Feed entries
53+
/// Return subscribed latest entries order by published time.
5754
async fn entries<'cx>(
5855
&self,
5956
cx: &Context<'_>,
6057
after: Option<String>,
6158
#[graphql(default = 20)] first: Option<i32>,
62-
) -> Result<Connection<id::EntryId, FeedEntry<'cx>>> {
59+
) -> Result<Connection<id::EntryId, Entry<'cx>>> {
6360
let first = first.unwrap_or(20).min(200) as usize;
6461
let has_prev = after.is_some();
6562
let input = FetchEntriesInput {
@@ -73,19 +70,18 @@ impl Feeds {
7370
let has_next = entries.len() > first;
7471
let mut connection = Connection::new(has_prev, has_next);
7572

76-
let edges = entries.into_iter().map(move |(entry, feed_url)| {
77-
let meta = feeds
78-
.get(&feed_url)
79-
.expect("FeedMeta not found. this is a bug")
80-
.clone();
81-
let cursor = entry.id().into();
82-
let node = FeedEntry {
83-
feed: meta.into(),
84-
entry: entry.into(),
85-
};
86-
87-
Edge::new(cursor, node)
88-
});
73+
let edges = entries
74+
.into_iter()
75+
.take(first)
76+
.map(move |(entry, feed_url)| {
77+
let meta = feeds
78+
.get(&feed_url)
79+
.expect("FeedMeta not found. this is a bug")
80+
.clone();
81+
let cursor = entry.id().into();
82+
let node = Entry::new(meta, entry);
83+
Edge::new(cursor, node)
84+
});
8985

9086
connection.edges.extend(edges);
9187

@@ -100,8 +96,4 @@ impl Query {
10096
async fn subscription(&self) -> Subscription {
10197
Subscription {}
10298
}
103-
104-
async fn feeds(&self) -> Feeds {
105-
Feeds {}
106-
}
10799
}

syndapi/src/lib.rs

-5
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,3 @@ pub mod principal;
1010
pub mod serve;
1111
pub mod service;
1212
pub mod usecase;
13-
14-
#[cfg(feature = "foo")]
15-
mod hello {
16-
pub fn hello() {}
17-
}

0 commit comments

Comments
 (0)