Skip to content

Commit

Permalink
feat(term): add export command
Browse files Browse the repository at this point in the history
  • Loading branch information
ymgyt committed Mar 10, 2024
1 parent d2a1f55 commit 9bb7318
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 14 deletions.
16 changes: 16 additions & 0 deletions crates/synd_term/gql/query.gql
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,19 @@ fragment PageInfo on PageInfo {
hasNextPage
endCursor
}

query ExportSubscription($after: String, $first: Int!) {
output: subscription {
feeds(after: $after, first: $first) {
pageInfo {
hasNextPage
endCursor
}
nodes {
title
url
type
}
}
}
}
12 changes: 4 additions & 8 deletions crates/synd_term/src/cli/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,23 @@ pub enum CheckFormat {
/// Check application conditions
#[derive(Args, Debug)]
pub struct CheckCommand {
/// synd_api endpoint
#[arg(long, default_value = config::api::ENDPOINT, env = config::env::ENDPOINT)]
pub endpoint: Url,

#[arg(value_enum, long, default_value_t = CheckFormat::Human)]
pub format: CheckFormat,
}

impl CheckCommand {
#[allow(clippy::unused_self)]
pub async fn run(self) -> i32 {
if let Err(err) = self.check().await {
pub async fn run(self, endpoint: Url) -> i32 {
if let Err(err) = self.check(endpoint).await {
tracing::error!("{err:?}");
1
} else {
0
}
}

async fn check(self) -> anyhow::Result<()> {
let Self { endpoint, format } = self;
async fn check(self, endpoint: Url) -> anyhow::Result<()> {
let Self { format } = self;
let client = Client::new(endpoint, Duration::from_secs(10))?;

let api_health = client
Expand Down
52 changes: 52 additions & 0 deletions crates/synd_term/src/cli/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::time::Duration;

use anyhow::anyhow;
use clap::Args;
use url::Url;

use crate::{auth, client::Client};

/// Export subscribed feeds
#[derive(Args, Debug)]
pub struct ExportCommand {}

impl ExportCommand {
#[allow(clippy::unused_self)]
pub async fn run(self, endpoint: Url) -> i32 {
if let Err(err) = self.export(endpoint).await {
tracing::error!("{err:?}");
1
} else {
0
}
}

async fn export(self, endpoint: Url) -> anyhow::Result<()> {
let mut client = Client::new(endpoint, Duration::from_secs(10))?;

let credentials = auth::credential_from_cache()
.ok_or_else(|| anyhow!("You are not authenticated, try login in first"))?;
client.set_credential(credentials);

let mut after = None;
let mut exported_feeds = Vec::new();

loop {
let response = client.export_subscription(after.take(), 1).await?;
exported_feeds.extend(response.feeds);

if !response.page_info.has_next_page {
break;
}
after = response.page_info.end_cursor;
}

let output = serde_json::json! {{
"feeds": exported_feeds,
}};

serde_json::to_writer_pretty(std::io::stdout(), &output)?;

Ok(())
}
}
4 changes: 3 additions & 1 deletion crates/synd_term/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::config;

mod check;
mod clear;
mod export;

#[derive(Copy, Clone, PartialEq, Eq, Debug, clap::ValueEnum)]
pub enum Palette {
Expand Down Expand Up @@ -67,7 +68,7 @@ impl From<Palette> for tailwind::Palette {
#[command(version, propagate_version = true, name = "synd")]
pub struct Args {
/// synd_api endpoint
#[arg(long, default_value = config::api::ENDPOINT, env = config::env::ENDPOINT)]
#[arg(long, global = true, default_value = config::api::ENDPOINT, env = config::env::ENDPOINT)]
pub endpoint: Url,
/// Log file path
#[arg(long, default_value = config::log_path().into_os_string(), env = config::env::LOG_PATH)]
Expand All @@ -86,6 +87,7 @@ pub struct Args {
pub enum Command {
Clear(clear::ClearCommand),
Check(check::CheckCommand),
Export(export::ExportCommand),
}

pub fn parse() -> Args {
Expand Down
15 changes: 14 additions & 1 deletion crates/synd_term/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use thiserror::Error;
use tracing::{error, Span};
use url::Url;

use crate::{auth::Credential, config, types};
use crate::{auth::Credential, client::payload::ExportSubscriptionPayload, config, types};

use self::query::subscription::SubscriptionOutput;

Expand Down Expand Up @@ -142,6 +142,19 @@ impl Client {
Ok(response.output.into())
}

#[tracing::instrument(skip(self))]
pub async fn export_subscription(
&self,
after: Option<String>,
first: i64,
) -> anyhow::Result<ExportSubscriptionPayload> {
let var = query::export_subscription::Variables { after, first };
let request = query::ExportSubscription::build_query(var);
let response: query::export_subscription::ResponseData = self.request(&request).await?;

Ok(response.output.into())
}

#[tracing::instrument(skip_all, err(Display))]
async fn request<Body, ResponseData>(&self, body: &Body) -> anyhow::Result<ResponseData>
where
Expand Down
14 changes: 14 additions & 0 deletions crates/synd_term/src/client/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,17 @@ impl From<query::entries::EntriesOutput> for FetchEntriesPayload {
Self { entries, page_info }
}
}

pub struct ExportSubscriptionPayload {
pub feeds: Vec<types::ExportedFeed>,
pub page_info: types::PageInfo,
}

impl From<query::export_subscription::ExportSubscriptionOutput> for ExportSubscriptionPayload {
fn from(v: query::export_subscription::ExportSubscriptionOutput) -> Self {
Self {
feeds: v.feeds.nodes.into_iter().map(Into::into).collect(),
page_info: v.feeds.page_info.into(),
}
}
}
100 changes: 98 additions & 2 deletions crates/synd_term/src/client/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pub mod subscription {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "Subscription";
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n\nquery ExportSubscription($after: String, $first: Int!) {\n output: subscription {\n feeds(after: $after, first: $first) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n title\n url\n type\n }\n }\n }\n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand Down Expand Up @@ -141,7 +141,7 @@ pub mod entries {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "Entries";
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n" ;
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n\nquery ExportSubscription($after: String, $first: Int!) {\n output: subscription {\n feeds(after: $after, first: $first) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n title\n url\n type\n }\n }\n }\n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
Expand Down Expand Up @@ -210,3 +210,99 @@ impl graphql_client::GraphQLQuery for Entries {
}
}
}
pub struct ExportSubscription;
pub mod export_subscription {
#![allow(dead_code)]
use std::result::Result;
pub const OPERATION_NAME: &str = "ExportSubscription";
pub const QUERY : & str = "query Subscription($after: String, $first: Int) {\n output: subscription {\n feeds(after: $after, first: $first) {\n nodes {\n ...Feed\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Feed on Feed {\n id\n type\n title\n url\n updated\n websiteUrl\n description\n generator\n entries(first: 10) {\n nodes {\n ...EntryMeta\n }\n }\n links {\n nodes {\n ...Link\n }\n }\n authors {\n nodes\n }\n}\n\nfragment EntryMeta on Entry {\n title,\n published,\n updated,\n summary,\n}\n\nfragment Link on Link {\n href\n rel\n mediaType\n title \n}\n\nquery Entries($after: String, $first: Int!) {\n output: subscription {\n entries(after: $after, first: $first) {\n nodes {\n ...Entry\n }\n pageInfo {\n ...PageInfo\n }\n }\n }\n}\n\nfragment Entry on Entry {\n title\n published\n updated\n summary\n websiteUrl\n feed {\n ...FeedMeta\n }\n}\n\nfragment FeedMeta on FeedMeta {\n title\n url\n}\n\nfragment PageInfo on PageInfo {\n hasNextPage\n endCursor\n}\n\nquery ExportSubscription($after: String, $first: Int!) {\n output: subscription {\n feeds(after: $after, first: $first) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n title\n url\n type\n }\n }\n }\n}\n" ;
use super::*;
use serde::{Deserialize, Serialize};
#[allow(dead_code)]
type Boolean = bool;
#[allow(dead_code)]
type Float = f64;
#[allow(dead_code)]
type Int = i64;
#[allow(dead_code)]
type ID = String;
#[derive(Debug)]
pub enum FeedType {
ATOM,
RSS1,
RSS2,
RSS0,
JSON,
Other(String),
}
impl ::serde::Serialize for FeedType {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(match *self {
FeedType::ATOM => "ATOM",
FeedType::RSS1 => "RSS1",
FeedType::RSS2 => "RSS2",
FeedType::RSS0 => "RSS0",
FeedType::JSON => "JSON",
FeedType::Other(ref s) => &s,
})
}
}
impl<'de> ::serde::Deserialize<'de> for FeedType {
fn deserialize<D: ::serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s: String = ::serde::Deserialize::deserialize(deserializer)?;
match s.as_str() {
"ATOM" => Ok(FeedType::ATOM),
"RSS1" => Ok(FeedType::RSS1),
"RSS2" => Ok(FeedType::RSS2),
"RSS0" => Ok(FeedType::RSS0),
"JSON" => Ok(FeedType::JSON),
_ => Ok(FeedType::Other(s)),
}
}
}
#[derive(Serialize, Debug)]
pub struct Variables {
pub after: Option<String>,
pub first: Int,
}
impl Variables {}
#[derive(Deserialize, Debug)]
pub struct ResponseData {
pub output: ExportSubscriptionOutput,
}
#[derive(Deserialize, Debug)]
pub struct ExportSubscriptionOutput {
pub feeds: ExportSubscriptionOutputFeeds,
}
#[derive(Deserialize, Debug)]
pub struct ExportSubscriptionOutputFeeds {
#[serde(rename = "pageInfo")]
pub page_info: ExportSubscriptionOutputFeedsPageInfo,
pub nodes: Vec<ExportSubscriptionOutputFeedsNodes>,
}
#[derive(Deserialize, Debug)]
pub struct ExportSubscriptionOutputFeedsPageInfo {
#[serde(rename = "hasNextPage")]
pub has_next_page: Boolean,
#[serde(rename = "endCursor")]
pub end_cursor: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct ExportSubscriptionOutputFeedsNodes {
pub title: Option<String>,
pub url: String,
#[serde(rename = "type")]
pub type_: FeedType,
}
}
impl graphql_client::GraphQLQuery for ExportSubscription {
type Variables = export_subscription::Variables;
type ResponseData = export_subscription::ResponseData;
fn build_query(variables: Self::Variables) -> ::graphql_client::QueryBody<Self::Variables> {
graphql_client::QueryBody {
variables,
query: export_subscription::QUERY,
operation_name: export_subscription::OPERATION_NAME,
}
}
}
3 changes: 2 additions & 1 deletion crates/synd_term/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ async fn main() {
if let Some(command) = command {
let exit_code = match command {
cli::Command::Clear(clear) => clear.run(),
cli::Command::Check(check) => check.run().await,
cli::Command::Check(check) => check.run(endpoint).await,
cli::Command::Export(export) => export.run(endpoint).await,
};

std::process::exit(exit_code);
Expand Down
24 changes: 23 additions & 1 deletion crates/synd_term/src/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use chrono::DateTime;
use serde::Serialize;
use synd_feed::types::FeedType;

use crate::client::{mutation, query};
use crate::client::{
mutation,
query::{self, export_subscription},
};

mod time;
pub use time::{Time, TimeExt};
Expand Down Expand Up @@ -172,6 +176,24 @@ impl From<query::entries::Entry> for Entry {
}
}

#[derive(Serialize)]
pub struct ExportedFeed {
pub title: Option<String>,
pub url: String,
// does not convert to utilize generated serde::Serialize impl
pub r#type: export_subscription::FeedType,
}

impl From<query::export_subscription::ExportSubscriptionOutputFeedsNodes> for ExportedFeed {
fn from(v: query::export_subscription::ExportSubscriptionOutputFeedsNodes) -> Self {
Self {
title: v.title,
url: v.url,
r#type: v.type_,
}
}
}

fn parse_time(t: impl AsRef<str>) -> Time {
DateTime::parse_from_rfc3339(t.as_ref())
.expect("invalid rfc3339 time")
Expand Down
9 changes: 9 additions & 0 deletions crates/synd_term/src/types/page_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,12 @@ impl From<query::entries::PageInfo> for PageInfo {
}
}
}

impl From<query::export_subscription::ExportSubscriptionOutputFeedsPageInfo> for PageInfo {
fn from(v: query::export_subscription::ExportSubscriptionOutputFeedsPageInfo) -> Self {
Self {
has_next_page: v.has_next_page,
end_cursor: v.end_cursor,
}
}
}

0 comments on commit 9bb7318

Please sign in to comment.