mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
feat(blog::api) impl GET /blog/posts
This commit is contained in:
5 files changed
+158
-15
No files matched your search
@@ -1,5 +1,110 @@
|
|||||||
use axum::response::IntoResponse;
|
use crate::{
|
||||||
|
constants::blog::PAGE_SIZE,
|
||||||
|
err_resp_log,
|
||||||
|
utils::axum_response::{err_resp, succ_resp},
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn handler() -> impl IntoResponse {
|
use axum::{
|
||||||
()
|
extract::{Query, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
use sqlx::{query_as, FromRow, PgPool};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(FromRow, Serialize)]
|
||||||
|
struct PostBriefModel {
|
||||||
|
id: Uuid,
|
||||||
|
identifier: String,
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
tags: Vec<String>,
|
||||||
|
category: String,
|
||||||
|
date_created: NaiveDate,
|
||||||
|
date_updated: Option<NaiveDate>,
|
||||||
|
header_img: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
|
pub struct Pagination {
|
||||||
|
page: Option<u32>,
|
||||||
|
page_size: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct RespPagination {
|
||||||
|
page: u32,
|
||||||
|
page_size: u32,
|
||||||
|
page_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Response {
|
||||||
|
posts: Vec<PostBriefModel>,
|
||||||
|
pagination: RespPagination,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
struct UuidRow {
|
||||||
|
id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
const GET_POST_WITH_BRIEF_MODEL: &str = include_str!("get_post_with_brief_model.sql");
|
||||||
|
|
||||||
|
pub async fn handler(
|
||||||
|
State(db): State<PgPool>,
|
||||||
|
Query(pagination): Query<Pagination>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let page = pagination.page.unwrap_or(1);
|
||||||
|
let page_size = pagination.page_size.unwrap_or(PAGE_SIZE);
|
||||||
|
let page_count = match sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM posts")
|
||||||
|
.fetch_one(&db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(post_count) => ((post_count as f64) / (page_size as f64)).ceil() as u32,
|
||||||
|
Err(e) => return err_resp_log!(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pagination = RespPagination {
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
page_count,
|
||||||
|
};
|
||||||
|
|
||||||
|
if page > page_count {
|
||||||
|
return err_resp(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"page number should not be greater than page count",
|
||||||
|
Some(json!({"pagination": pagination})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = page_size * (page - 1);
|
||||||
|
|
||||||
|
let ids = match query_as::<_, UuidRow>(
|
||||||
|
"SELECT id FROM posts ORDER BY date_created DESC LIMIT $1 OFFSET $2",
|
||||||
|
)
|
||||||
|
.bind(page_size as i32)
|
||||||
|
.bind(offset as i32)
|
||||||
|
.fetch_all(&db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => res.into_iter().map(|item| item.id).collect::<Vec<_>>(),
|
||||||
|
Err(e) => return err_resp_log!(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let posts = match query_as::<_, PostBriefModel>(GET_POST_WITH_BRIEF_MODEL)
|
||||||
|
.bind(&ids)
|
||||||
|
.fetch_all(&db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(posts) => posts,
|
||||||
|
Err(e) => return err_resp_log!(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let resp = Response { posts, pagination };
|
||||||
|
succ_resp(StatusCode::OK, serde_json::to_value(resp).unwrap())
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::utils::axum_response::{err_resp, succ_resp};
|
use crate::{err_resp_log, utils::axum_response::succ_resp};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
@@ -73,8 +73,10 @@ pub async fn handler(
|
|||||||
Ok(post) => post,
|
Ok(post) => post,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return match e {
|
return match e {
|
||||||
sqlx::Error::RowNotFound => err_resp(StatusCode::NOT_FOUND, "no matching post"),
|
sqlx::Error::RowNotFound => {
|
||||||
_ => err_resp(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
err_resp_log!(StatusCode::NOT_FOUND, "no matching post")
|
||||||
|
}
|
||||||
|
_ => err_resp_log!(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
SELECT p.id,
|
||||||
|
identifier,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
c.name AS category,
|
||||||
|
(SELECT array_agg(t.name)
|
||||||
|
FROM tags t
|
||||||
|
JOIN post_tag pt ON pt.tag = t.id
|
||||||
|
WHERE pt.post = p.id) AS tags,
|
||||||
|
(SELECT m.meta_value
|
||||||
|
FROM metadata m
|
||||||
|
WHERE m.pid = p.id
|
||||||
|
AND m.meta_key = 'header_img') AS header_img
|
||||||
|
FROM posts p
|
||||||
|
JOIN public.categories c on c.id = p.category
|
||||||
|
WHERE p.id = ANY ($1)
|
||||||
|
ORDER BY p.date_created DESC;
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
use crate::{
|
use crate::{constants::blog::PAGE_SIZE, err_resp_log, utils::axum_response::succ_resp};
|
||||||
constants::blog::PAGE_SIZE,
|
|
||||||
utils::axum_response::{err_resp, succ_resp},
|
|
||||||
};
|
|
||||||
|
|
||||||
use axum::{extract::State, http::StatusCode, response::Response};
|
use axum::{extract::State, http::StatusCode, response::Response};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
@@ -50,6 +47,6 @@ pub async fn handler(State(db_connection): State<PgPool>) -> Response<String> {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => succ_resp(StatusCode::OK, PaginationResponse::from(res).into()),
|
Ok(res) => succ_resp(StatusCode::OK, PaginationResponse::from(res).into()),
|
||||||
Err(e) => err_resp(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
Err(e) => err_resp_log!(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,13 +4,33 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
pub fn err_resp(code: StatusCode, err_msg: &str) -> Response<String> {
|
#[macro_export]
|
||||||
let payload = json!({
|
macro_rules! err_resp_log {
|
||||||
|
($code:expr, $msg:expr) => {{
|
||||||
|
use tracing::error;
|
||||||
|
use $crate::utils::axum_response::err_resp;
|
||||||
|
|
||||||
|
error!("{}", $msg);
|
||||||
|
err_resp($code, $msg, None)
|
||||||
|
}};
|
||||||
|
($code:expr, $msg:expr, $body:expr) => {{
|
||||||
|
use tracing::error;
|
||||||
|
use $crate::utils::axum_response::err_resp;
|
||||||
|
|
||||||
|
error!("{}", $msg);
|
||||||
|
err_resp($code, $msg, Some($body))
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err_resp(code: StatusCode, err_msg: &str, body: Option<Value>) -> Response<String> {
|
||||||
|
let mut payload = json!({
|
||||||
"status": "error",
|
"status": "error",
|
||||||
"data": {
|
|
||||||
"msg": err_msg,
|
"msg": err_msg,
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
if let Some(body) = body {
|
||||||
|
payload["data"] = body;
|
||||||
|
}
|
||||||
|
|
||||||
resp_json(code, payload)
|
resp_json(code, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user