1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use std::sync::Arc;

use synd_feed::{feed::cache::FetchCachedFeed, types};
use thiserror::Error;

use crate::{
    principal::Principal,
    repository::SubscriptionRepository,
    usecase::{authorize::Unauthorized, Error, Input, MakeUsecase, Output, Usecase},
};

pub struct FetchSubscribedFeeds {
    pub repository: Arc<dyn SubscriptionRepository>,
    pub fetch_feed: Arc<dyn FetchCachedFeed>,
}

pub struct FetchSubscribedFeedsInput {
    pub after: Option<String>,
    pub first: usize,
}

#[derive(Default)]
pub struct FetchSubscribedFeedsOutput {
    pub feeds: Vec<Arc<types::Feed>>,
}

#[derive(Error, Debug)]
pub enum FetchSubscribedFeedsError {}

impl Usecase for FetchSubscribedFeeds {
    type Input = FetchSubscribedFeedsInput;

    type Output = FetchSubscribedFeedsOutput;

    type Error = FetchSubscribedFeedsError;

    fn new(make: &MakeUsecase) -> Self {
        Self {
            repository: make.subscription_repo.clone(),
            fetch_feed: make.fetch_feed.clone(),
        }
    }

    async fn authorize(
        &self,
        principal: Principal,
        _: &Self::Input,
    ) -> Result<Principal, Unauthorized> {
        Ok(principal)
    }

    async fn usecase(
        &self,
        Input {
            principal,
            input: FetchSubscribedFeedsInput { after, first },
        }: Input<Self::Input>,
    ) -> Result<Output<Self::Output>, Error<Self::Error>> {
        let user_id = principal.user_id().unwrap();

        // fetch all urls from repository
        let urls = self.repository.fetch_subscribed_feed_urls(user_id).await?;

        // paginate
        let urls = {
            let start = after
                .and_then(|after| urls.iter().position(|url| url == &after).map(|p| p + 1))
                .unwrap_or(0);
            if start >= urls.len() {
                return Ok(Output {
                    output: FetchSubscribedFeedsOutput::default(),
                });
            }
            let urls = &urls[start..];
            let end = (start + first).min(urls.len());
            &urls[..end]
        };

        // fetch feeds
        let feeds = self.fetch_feed.fetch_feeds_parallel(urls).await;

        // TODO: return failed feeds
        let (feeds, errors): (Vec<_>, Vec<_>) = feeds.into_iter().partition(Result::is_ok);

        if !errors.is_empty() {
            tracing::error!("{errors:?}");
        }

        let feeds = feeds.into_iter().map(Result::unwrap).collect();

        Ok(Output {
            output: FetchSubscribedFeedsOutput { feeds },
        })
    }
}