tag.rs (3025B)
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::{Category, StoryWithMeta}; 10 use crate::database::{self, TARGET_TYPE_STORY}; 11 use crate::error::AppError; 12 use crate::state::AppState; 13 use crate::templates::{ApplicationTemplate, TagTemplate}; 14 15 const PAGE_LIMIT: usize = 30; 16 17 #[derive(Debug, Deserialize)] 18 pub struct TagQuery { 19 #[serde(default)] 20 pub page: usize, 21 } 22 23 pub async fn show_tag( 24 State(state): State<AppState>, 25 jar: CookieJar, 26 Path(tag_name): Path<String>, 27 Query(query): Query<TagQuery>, 28 ) -> Result<Html<String>, AppError> { 29 let user_id = state.get_user_id(&jar).await; 30 let offset = query.page * PAGE_LIMIT; 31 let page = query.page; 32 let tag_name_clone = tag_name.clone(); 33 34 let db = state.db(); 35 // Find the category by name 36 let categories = db.get_categories(100).await?; 37 let category = categories 38 .iter() 39 .find(|c| c.name.as_deref() == Some(&tag_name_clone)) 40 .ok_or_else(|| AppError::NotFound(format!("Tag '{}' not found", tag_name_clone)))?; 41 42 let all_stories = db.get_stories_by_tag(category.id, PAGE_LIMIT + 1, offset).await?; 43 44 let reached_end = all_stories.len() <= PAGE_LIMIT; 45 let stories: Vec<_> = all_stories.into_iter().take(PAGE_LIMIT).collect(); 46 47 let story_ids: Vec<i64> = stories.iter().map(|s| s.id).collect(); 48 49 let voted_ids = if let Some(uid) = user_id { 50 if !story_ids.is_empty() { 51 db.get_user_votes(uid, TARGET_TYPE_STORY, &story_ids) 52 .await 53 .unwrap_or_default() 54 } else { 55 vec![] 56 } 57 } else { 58 vec![] 59 }; 60 61 let meta = db.enrich_stories(&story_ids).await.unwrap_or_default(); 62 63 let base_rank = page * PAGE_LIMIT; 64 let mut enriched_stories = Vec::with_capacity(stories.len()); 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: Vec<Category> = story_meta 69 .map(|m| m.tags.iter().map(|name| Category { id: 0, name: Some(name.clone()) }).collect()) 70 .unwrap_or_default(); 71 let author_username = story_meta.and_then(|m| m.author_username.clone()); 72 73 let time_ago = database::time_ago(story.published); 74 let user_voted = voted_ids.contains(&story.id); 75 let rank = base_rank + idx + 1; 76 77 enriched_stories.push(StoryWithMeta::from_story( 78 story, 79 tags, 80 author_username, 81 time_ago, 82 user_voted, 83 rank, 84 )); 85 } 86 87 let stories = enriched_stories; 88 89 let is_logged_in = user_id.is_some(); 90 91 let tag_template = TagTemplate { 92 tag_name, 93 stories, 94 page: query.page, 95 reached_end, 96 is_logged_in, 97 }; 98 99 let app = ApplicationTemplate { 100 content: tag_template.render()?, 101 }; 102 Ok(Html(app.render()?)) 103 }