1use crate::api::error::ApiError;
9use leptos::prelude::*;
10
11#[cfg(feature = "server")]
12use super::helpers::{get_current_admin_user, row_to_post_full};
13use super::types::{PublicPostResponse, SinglePostResponse};
14#[cfg(feature = "server")]
15use crate::api::error::AppError;
16#[cfg(feature = "server")]
17use crate::db::pool::get_conn;
18
19#[server(GetPostById, endpoint = "get_post_by_id", input = leptos::server_fn::codec::Json)]
23pub async fn get_post_by_id(post_id: i32) -> Result<SinglePostResponse, ApiError> {
24 let _user = get_current_admin_user().await?;
25
26 #[cfg(feature = "server")]
27 {
28 let client = get_conn().await.map_err(AppError::db_conn)?;
29
30 let row = client
31 .query_opt(
32 "SELECT
33 p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
34 p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
35 p.word_count, p.reading_time,
36 COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
37 FROM posts p
38 LEFT JOIN post_tags pt ON p.id = pt.post_id
39 LEFT JOIN tags t ON pt.tag_id = t.id
40 WHERE p.id = $1 AND p.deleted_at IS NULL
41 GROUP BY p.id",
42 &[&post_id],
43 )
44 .await
45 .map_err(AppError::query)?;
46
47 let post = match row {
48 Some(row) => Some(row_to_post_full(&row).await?),
49 None => None,
50 };
51
52 Ok(SinglePostResponse { post })
53 }
54
55 #[cfg(not(feature = "server"))]
56 {
57 Ok(SinglePostResponse { post: None })
58 }
59}
60
61#[server(GetPostBySlug, endpoint = "get_post_by_slug", input = leptos::server_fn::codec::Json)]
65pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, ApiError> {
66 #[cfg(feature = "server")]
67 {
68 if let Some(cached) = crate::cache::get_post_by_slug(&slug).await {
69 return Ok(SinglePostResponse { post: cached });
70 }
71
72 let client = get_conn().await.map_err(AppError::db_conn)?;
73
74 let row = client
76 .query_opt(
77 "SELECT
78 p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
79 p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
80 p.word_count, p.reading_time,
81 COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
82 prev.title as prev_title, prev.slug as prev_slug,
83 next.title as next_title, next.slug as next_slug
84 FROM posts p
85 LEFT JOIN post_tags pt ON p.id = pt.post_id
86 LEFT JOIN tags t ON pt.tag_id = t.id
87 LEFT JOIN LATERAL (
88 SELECT title, slug FROM posts
89 WHERE published_at < p.published_at
90 AND status = 'published'
91 AND deleted_at IS NULL
92 ORDER BY published_at DESC
93 LIMIT 1
94 ) prev ON true
95 LEFT JOIN LATERAL (
96 SELECT title, slug FROM posts
97 WHERE published_at > p.published_at
98 AND status = 'published'
99 AND deleted_at IS NULL
100 ORDER BY published_at ASC
101 LIMIT 1
102 ) next ON true
103 WHERE p.slug = $1 AND p.status = 'published' AND p.deleted_at IS NULL
104 GROUP BY p.id, prev.title, prev.slug, next.title, next.slug",
105 &[&slug],
106 )
107 .await
108 .map_err(AppError::query)?;
109
110 let post = match row {
111 Some(row) => Some(row_to_post_full(&row).await?),
112 None => None,
113 };
114
115 if post.is_some() {
116 crate::cache::set_post_by_slug(&slug, post.clone()).await;
117 }
118 Ok(SinglePostResponse { post })
119 }
120
121 #[cfg(not(feature = "server"))]
122 {
123 Ok(SinglePostResponse { post: None })
124 }
125}
126
127#[server(GetPublicPostBySlug, endpoint = "get_public_post_by_slug", input = leptos::server_fn::codec::Json)]
129pub async fn get_public_post_by_slug(slug: String) -> Result<PublicPostResponse, ApiError> {
130 let response = get_post_by_slug(slug).await?;
131 Ok(PublicPostResponse {
132 post: response.post.map(Into::into),
133 })
134}
135
136#[server(GetPostPreview, endpoint = "get_post_preview", input = leptos::server_fn::codec::Json)]
142pub async fn get_post_preview(slug: String) -> Result<SinglePostResponse, ApiError> {
143 let _user = get_current_admin_user().await?;
144
145 #[cfg(feature = "server")]
146 {
147 let client = get_conn().await.map_err(AppError::db_conn)?;
148
149 let row = client
150 .query_opt(
151 "SELECT
152 p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
153 p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
154 p.word_count, p.reading_time,
155 COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
156 FROM posts p
157 LEFT JOIN post_tags pt ON p.id = pt.post_id
158 LEFT JOIN tags t ON pt.tag_id = t.id
159 WHERE p.slug = $1 AND p.deleted_at IS NULL
160 GROUP BY p.id",
161 &[&slug],
162 )
163 .await
164 .map_err(AppError::query)?;
165
166 let post = match row {
167 Some(row) => Some(row_to_post_full(&row).await?),
168 None => None,
169 };
170
171 Ok(SinglePostResponse { post })
172 }
173
174 #[cfg(not(feature = "server"))]
175 {
176 Ok(SinglePostResponse { post: None })
177 }
178}