user.rs (3619B)
1 use askama::Template; 2 use axum::{ 3 extract::{Path, Query, State}, 4 response::Html, 5 }; 6 use axum_extra::extract::cookie::CookieJar; 7 use serde::Deserialize; 8 9 use crate::database::{CommentWithMeta, StoryWithMeta, UserProfile}; 10 use crate::database::{self, TARGET_TYPE_STORY}; 11 use crate::error::AppError; 12 use crate::state::AppState; 13 use crate::templates::{ApplicationTemplate, UserTemplate}; 14 15 #[derive(Debug, Deserialize)] 16 pub struct UserQuery { 17 #[serde(default = "default_tab")] 18 pub tab: String, 19 } 20 21 fn default_tab() -> String { 22 "submissions".to_string() 23 } 24 25 pub async fn show_user( 26 State(state): State<AppState>, 27 jar: CookieJar, 28 Path(username): Path<String>, 29 Query(query): Query<UserQuery>, 30 ) -> Result<Html<String>, AppError> { 31 let current_user_id = state.get_user_id(&jar).await; 32 let tab = query.tab.clone(); 33 let username_clone = username.clone(); 34 35 let db = state.db(); 36 // Get user by username 37 let user = db.get_user_by_username(&username_clone).await? 38 .ok_or_else(|| AppError::NotFound(format!("User '{}' not found", username_clone)))?; 39 let profile: UserProfile = user.into(); 40 41 let mut submissions: Vec<StoryWithMeta> = Vec::new(); 42 let mut comments: Vec<CommentWithMeta> = Vec::new(); 43 44 let user_id_num: i64 = profile.user_id.parse().unwrap_or(0); 45 46 match tab.as_str() { 47 "submissions" => { 48 let stories = db.get_stories_by_user(user_id_num, 30, 0).await?; 49 50 let story_ids: Vec<i64> = stories.iter().map(|s| s.id).collect(); 51 let voted_ids = if let Some(uid) = current_user_id { 52 if !story_ids.is_empty() { 53 db.get_user_votes(uid, TARGET_TYPE_STORY, &story_ids) 54 .await 55 .unwrap_or_default() 56 } else { 57 vec![] 58 } 59 } else { 60 vec![] 61 }; 62 63 // Batch fetch tags 64 let meta = db.enrich_stories(&story_ids).await.unwrap_or_default(); 65 66 for (idx, story) in stories.into_iter().enumerate() { 67 let story_meta = meta.iter().find(|m| m.story_id == story.id); 68 let tags = story_meta 69 .map(|m| m.tags.iter().map(|name| database::Category { id: 0, name: Some(name.clone()) }).collect()) 70 .unwrap_or_default(); 71 let time_ago = database::time_ago(story.published); 72 let user_voted = voted_ids.contains(&story.id); 73 74 submissions.push(StoryWithMeta::from_story( 75 story, 76 tags, 77 Some(profile.username.clone()), 78 time_ago, 79 user_voted, 80 idx + 1, 81 )); 82 } 83 } 84 "comments" => { 85 let user_comments = db.get_comments_by_user(user_id_num, 30, 0).await?; 86 87 for comment in user_comments { 88 let time_ago = database::time_ago(comment.created_at); 89 comments.push(CommentWithMeta::from_comment( 90 comment, 91 Some(profile.username.clone()), 92 time_ago, 93 false, 94 )); 95 } 96 } 97 _ => {} 98 } 99 100 let is_logged_in = current_user_id.is_some(); 101 102 let user_template = UserTemplate { 103 profile, 104 submissions, 105 comments, 106 tab: query.tab, 107 is_logged_in, 108 }; 109 110 let app = ApplicationTemplate { 111 content: user_template.render()?, 112 }; 113 Ok(Html(app.render()?)) 114 }