yggdrasil/api/posts/
read.rs1use dioxus::prelude::*;
9
10#[cfg(feature = "server")]
11use super::helpers::{get_current_admin_user, row_to_post_full};
12use super::types::SinglePostResponse;
13#[cfg(feature = "server")]
14use crate::api::error::AppError;
15#[cfg(feature = "server")]
16use crate::db::pool::get_conn;
17
18#[server(GetPostById, "/api")]
22pub async fn get_post_by_id(post_id: i32) -> Result<SinglePostResponse, ServerFnError> {
23 let _user = get_current_admin_user().await?;
24
25 #[cfg(feature = "server")]
26 {
27 let client = get_conn().await.map_err(AppError::db_conn)?;
28
29 let row = client
30 .query_opt(
31 "SELECT
32 p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
33 p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
34 p.word_count, p.reading_time,
35 COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
36 FROM posts p
37 LEFT JOIN post_tags pt ON p.id = pt.post_id
38 LEFT JOIN tags t ON pt.tag_id = t.id
39 WHERE p.id = $1 AND p.deleted_at IS NULL
40 GROUP BY p.id",
41 &[&post_id],
42 )
43 .await
44 .map_err(AppError::query)?;
45
46 let post = match row {
47 Some(row) => Some(row_to_post_full(&row).await),
48 None => None,
49 };
50
51 Ok(SinglePostResponse { post })
52 }
53
54 #[cfg(not(feature = "server"))]
55 {
56 Ok(SinglePostResponse { post: None })
57 }
58}
59
60#[server(GetPostBySlug, "/api")]
64pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, ServerFnError> {
65 #[cfg(feature = "server")]
66 {
67 if let Some(cached) = crate::cache::get_post_by_slug(&slug).await {
68 return Ok(SinglePostResponse { post: cached });
69 }
70
71 let client = get_conn().await.map_err(AppError::db_conn)?;
72
73 let row = client
75 .query_opt(
76 "SELECT
77 p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
78 p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
79 p.word_count, p.reading_time,
80 COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
81 prev.title as prev_title, prev.slug as prev_slug,
82 next.title as next_title, next.slug as next_slug
83 FROM posts p
84 LEFT JOIN post_tags pt ON p.id = pt.post_id
85 LEFT JOIN tags t ON pt.tag_id = t.id
86 LEFT JOIN LATERAL (
87 SELECT title, slug FROM posts
88 WHERE published_at < p.published_at
89 AND status = 'published'
90 AND deleted_at IS NULL
91 ORDER BY published_at DESC
92 LIMIT 1
93 ) prev ON true
94 LEFT JOIN LATERAL (
95 SELECT title, slug FROM posts
96 WHERE published_at > p.published_at
97 AND status = 'published'
98 AND deleted_at IS NULL
99 ORDER BY published_at ASC
100 LIMIT 1
101 ) next ON true
102 WHERE p.slug = $1 AND p.status = 'published' AND p.deleted_at IS NULL
103 GROUP BY p.id, prev.title, prev.slug, next.title, next.slug",
104 &[&slug],
105 )
106 .await
107 .map_err(AppError::query)?;
108
109 let post = match row {
110 Some(row) => Some(row_to_post_full(&row).await),
111 None => None,
112 };
113
114 if post.is_some() {
115 crate::cache::set_post_by_slug(&slug, post.clone()).await;
116 }
117 Ok(SinglePostResponse { post })
118 }
119
120 #[cfg(not(feature = "server"))]
121 {
122 Ok(SinglePostResponse { post: None })
123 }
124}