nyantrack_common/
user.rs

1pub mod permissions;
2pub mod settings;
3
4use std::sync::Arc;
5
6use super::lists::Lists;
7use permissions::Permission;
8use regex::Regex;
9use serde::{
10    de::{Unexpected, Visitor},
11    Deserialize, Serialize,
12};
13use settings::Settings;
14
15/// A user of the instance
16#[derive(Debug, Serialize, Deserialize)]
17pub struct User {
18    /// ActivityPub Actor ID
19    actor_id: ActorID,
20    /// The user's username
21    username: String,
22    /// The users' anime/manga lists
23    lists: Lists,
24    /// The URL of the user's profile picture
25    pfp: Option<String>,
26    /// The URL of the user's banner image
27    banner: Option<String>,
28    /// The Actor IDs of the users this user follows
29    following: Vec<Arc<str>>,
30    /// The Actor IDs of the users that follow this user
31    followers: Vec<Arc<str>>,
32    /// The user's settings
33    settings: Settings,
34    /// The user's permissions
35    permissions: Vec<Permission>,
36}
37
38/// An ActivityPub Actor ID for a NyanTrack instance. Format is
39/// "{instance_url}/user/{user_id}". This does not represent Actor IDs
40/// for other fediverse software, such as Mastodon, since these may have any
41/// arbitrary format.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct ActorID {
44    instance_url: Arc<str>,
45    user_id: u64,
46}
47
48impl ActorID {
49    pub fn new(instance_url: &str, user_id: u64) -> Self {
50        Self {
51            instance_url: instance_url.into(),
52            user_id,
53        }
54    }
55
56    pub fn to_string(&self) -> String {
57        self.into()
58    }
59}
60
61impl From<ActorID> for String {
62    fn from(value: ActorID) -> Self {
63        format!("{}/user/{}", value.instance_url, value.user_id)
64    }
65}
66
67impl From<&ActorID> for String {
68    fn from(value: &ActorID) -> Self {
69        Self::from(value.clone())
70    }
71}
72
73impl Serialize for ActorID {
74    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75    where
76        S: serde::Serializer,
77    {
78        serializer.collect_str(&self.to_string())
79    }
80}
81
82struct ActorIDVisitor;
83static USERID_EXPECTED: &str = "an integer between 0 and 2^64";
84static EXPECTED: &str = "a URL of the form {instance_url}/user/{user_id}, where `user_id` is an integer between 0 and 2^64";
85
86impl<'de> Visitor<'de> for ActorIDVisitor {
87    type Value = ActorID;
88
89    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
90        formatter.write_str(EXPECTED)
91    }
92
93    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
94    where
95        E: serde::de::Error,
96    {
97        // Matches only a url in the correct ActorID format, and captures the
98        // instance url and user ID
99        let actorid_re =
100            Regex::new(r"^(?:((?:https?://)?(?:[^:/$]+)(?::(?:\d+))?)(?:/user/(\d+)))$")
101                .expect("Hardcoded valid regex");
102
103        // Parse instance URL and user ID out of the input str
104        let Some((_, [instance_url, user_id])) = actorid_re.captures(v).map(|c| c.extract()) else {
105            return Err(E::invalid_value(Unexpected::Str(v), &EXPECTED));
106        };
107        let Ok(user_id) = user_id.parse::<u64>() else {
108            return Err(E::invalid_type(
109                Unexpected::Other(user_id),
110                &USERID_EXPECTED,
111            ));
112        };
113
114        Ok(Self::Value {
115            instance_url: instance_url.into(),
116            user_id,
117        })
118    }
119
120    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
121    where
122        E: serde::de::Error,
123    {
124        self.visit_str(&v)
125    }
126}
127
128impl<'de> Deserialize<'de> for ActorID {
129    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
130    where
131        D: serde::Deserializer<'de>,
132    {
133        deserializer.deserialize_any(ActorIDVisitor)
134    }
135}