types.rs
1 use chrono::{DateTime, Utc}; 2 use serde::{Deserialize, Serialize}; 3 4 #[derive(Copy, Clone, PartialEq)] 5 pub enum StorySorting { 6 Top, 7 New, 8 Best, 9 Show, 10 Ask, 11 Job, 12 } 13 14 const TOP: &'static str = "top"; 15 const BEST: &'static str = "best"; 16 const NEW: &'static str = "new"; 17 const SHOW: &'static str = "show"; 18 const ASK: &'static str = "ask"; 19 const JOB: &'static str = "job"; 20 21 impl StorySorting { 22 /// return all of the story sorting possible 23 pub fn all() -> Vec<Self> { 24 vec![ 25 StorySorting::Top, 26 StorySorting::Best, 27 StorySorting::New, 28 StorySorting::Show, 29 StorySorting::Ask, 30 StorySorting::Job, 31 ] 32 } 33 /// match url to StorySorting (supports both hash and path routing) 34 pub fn from_url(url: &str) -> Option<Self> { 35 let hash_url = url.strip_prefix("#").unwrap_or(url); 36 Self::all().into_iter().find(|s| hash_url == s.to_str() || url == s.to_url()) 37 } 38 /// return the str for assembling paths in warp 39 pub fn to_str(&self) -> &str { 40 match self { 41 StorySorting::Top => TOP, 42 StorySorting::Best => BEST, 43 StorySorting::New => NEW, 44 StorySorting::Show => SHOW, 45 StorySorting::Ask => ASK, 46 StorySorting::Job => JOB, 47 } 48 } 49 50 pub fn to_url(&self) -> String { 51 format!("#{}", self.to_str()) 52 } 53 } 54 55 impl Default for StorySorting { 56 fn default() -> Self { 57 StorySorting::Top 58 } 59 } 60 61 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 62 pub struct StoryPageData { 63 pub id: i64, 64 pub title: String, 65 pub url: Option<String>, 66 pub text: Option<String>, 67 #[serde(default)] 68 pub by: String, 69 #[serde(default)] 70 pub score: i64, 71 #[serde(default)] 72 pub descendants: i64, 73 #[serde(with = "chrono::serde::ts_seconds")] 74 pub time: DateTime<Utc>, 75 #[serde(default)] 76 pub kids: Vec<i64>, 77 pub r#type: String, 78 #[serde(default)] 79 pub comments: Vec<Comment>, 80 } 81 82 impl StoryPageData { 83 /// derive a preview of this StoragePageData 84 pub fn preview(&self) -> StoryItem { 85 StoryItem { 86 id: self.id, 87 title: self.title.to_owned(), 88 url: self.url.to_owned(), 89 text: self.text.to_owned(), 90 by: self.by.to_owned(), 91 score: self.score, 92 descendants: self.descendants, 93 time: self.time.to_owned(), 94 kids: self.kids.to_owned(), 95 r#type: self.r#type.to_owned(), 96 } 97 } 98 } 99 100 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 101 pub struct Comment { 102 pub id: i64, 103 /// there will be no by field if the comment was deleted 104 #[serde(default)] 105 pub by: String, 106 #[serde(default)] 107 pub text: String, 108 #[serde(with = "chrono::serde::ts_seconds")] 109 pub time: DateTime<Utc>, 110 #[serde(default)] 111 pub kids: Vec<i64>, 112 #[serde(default)] 113 pub sub_comments: Vec<Comment>, 114 pub r#type: String, 115 } 116 117 impl Comment { 118 /// attempt to extract comment id from url (supports both hash and path routing) 119 pub fn id_from_url(url: &str) -> Option<i64> { 120 let target_url = url.strip_prefix("#").unwrap_or(url); 121 if target_url.starts_with("comment/") || url.starts_with("/comment") { 122 let splinters = if target_url.starts_with("comment/") { 123 target_url.split("/").collect::<Vec<_>>() 124 } else { 125 url.split("/").collect::<Vec<_>>() 126 }; 127 if splinters.len() >= 2 { 128 if target_url.starts_with("comment/") { 129 splinters[1].parse::<i64>().ok() 130 } else { 131 splinters[2].parse::<i64>().ok() 132 } 133 } else { 134 None 135 } 136 } else { 137 None 138 } 139 } 140 141 pub fn to_url(comment_id: i64) -> String { 142 format!("#comment/{}", comment_id) 143 } 144 } 145 146 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 147 pub struct StoryItem { 148 pub id: i64, 149 pub title: String, 150 pub url: Option<String>, 151 pub text: Option<String>, 152 #[serde(default)] 153 pub by: String, 154 #[serde(default)] 155 pub score: i64, 156 #[serde(default)] 157 pub descendants: i64, 158 #[serde(with = "chrono::serde::ts_seconds")] 159 pub time: DateTime<Utc>, 160 #[serde(default)] 161 pub kids: Vec<i64>, 162 pub r#type: String, 163 } 164 165 impl StoryItem { 166 /// attempt to extract story id from url (supports both hash and path routing) 167 pub fn id_from_url(url: &str) -> Option<i64> { 168 let target_url = url.strip_prefix("#").unwrap_or(url); 169 if target_url.starts_with("item/") || url.starts_with("/item") { 170 let splinters = if target_url.starts_with("item/") { 171 target_url.split("/").collect::<Vec<_>>() 172 } else { 173 url.split("/").collect::<Vec<_>>() 174 }; 175 if splinters.len() >= 2 { 176 if target_url.starts_with("item/") { 177 splinters[1].parse::<i64>().ok() 178 } else { 179 splinters[2].parse::<i64>().ok() 180 } 181 } else { 182 None 183 } 184 } else { 185 None 186 } 187 } 188 189 pub fn to_url(story_id: i64) -> String { 190 format!("#item/{}", story_id) 191 } 192 } 193 194 #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 195 pub struct UserData { 196 pub id: String, 197 pub karma: i64, 198 #[serde(default)] 199 pub about: String, 200 #[serde(default)] 201 pub submitted: Vec<i64>, 202 #[serde(default)] 203 pub stories: Vec<StoryItem>, 204 } 205 206 impl UserData { 207 /// attempt to extract user id from url (supports both hash and path routing) 208 pub fn id_from_url(url: &str) -> Option<String> { 209 let target_url = url.strip_prefix("#").unwrap_or(url); 210 if target_url.starts_with("user/") || url.starts_with("/user") { 211 let splinters = if target_url.starts_with("user/") { 212 target_url.split("/").collect::<Vec<_>>() 213 } else { 214 url.split("/").collect::<Vec<_>>() 215 }; 216 if splinters.len() >= 2 { 217 if target_url.starts_with("user/") { 218 Some(splinters[1].to_string()) 219 } else { 220 Some(splinters[2].to_string()) 221 } 222 } else { 223 None 224 } 225 } else { 226 None 227 } 228 } 229 230 pub fn to_url(username: &str) -> String { 231 format!("#user/{}", username) 232 } 233 }