simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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 }