mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 10:26:21 +00:00
Compare commits
2
Commits
80a164625b
...
2f6cafa543
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f6cafa543 | ||
|
|
dc2c294617 |
No files matched your search
@@ -1,32 +1,5 @@
|
||||
use macros::dev_consume;
|
||||
use axum::response::IntoResponse;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde_json::json;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn handler(
|
||||
State(db_connection): State<PgPool>,
|
||||
Path(identifier): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
if let Ok(id) = identifier.parse::<Uuid>() {
|
||||
select_post_by_uuid(&db_connection, id).await
|
||||
} else {
|
||||
select_post_by_str_id(&db_connection, &identifier).await
|
||||
};
|
||||
Json::from(json!({"res": "ok"})).into_response()
|
||||
}
|
||||
|
||||
async fn select_post_by_uuid(db: &PgPool, id: Uuid) {
|
||||
dev_consume!(db, id);
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn select_post_by_str_id(db: &PgPool, id: &str) {
|
||||
dev_consume!(db, id);
|
||||
todo!()
|
||||
pub async fn handler() -> impl IntoResponse {
|
||||
()
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use crate::utils::axum_response::{err_resp, succ_resp};
|
||||
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use futures::future::OptionFuture;
|
||||
use serde_json::Value;
|
||||
use sqlx::{FromRow, PgPool, query_as};
|
||||
use uuid::Uuid;
|
||||
|
||||
const SELECT_POST_BY_UUID: &str = include_str!("get_post_by_uuid.sql");
|
||||
const SELECT_POST_BY_STR_ID: &str = include_str!("get_post_by_str_id.sql");
|
||||
const SELECT_POST_ADJACENT_IDS: &str = include_str!("get_post_adjacent_ids.sql");
|
||||
const SELECT_POST_WITH_ADJACENT_MODEL: &str = include_str!("get_post_with_adjacent_model.sql");
|
||||
|
||||
#[derive(serde::Serialize, FromRow)]
|
||||
struct SelectedPost {
|
||||
id: Uuid,
|
||||
identifier: String,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
status: String,
|
||||
date_created: NaiveDate,
|
||||
date_updated: Option<NaiveDate>,
|
||||
category: String,
|
||||
tags: Vec<String>,
|
||||
content: String,
|
||||
metadata: Value,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, FromRow)]
|
||||
struct AdjacentPost {
|
||||
id: Uuid,
|
||||
identifier: String,
|
||||
title: String,
|
||||
subtitle: String,
|
||||
date_created: NaiveDate,
|
||||
category: String,
|
||||
tags: Vec<String>,
|
||||
header_img: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Debug)]
|
||||
struct AdjacentIds {
|
||||
prev: Option<Uuid>,
|
||||
next: Option<Uuid>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, Default)]
|
||||
struct AdjacentPosts {
|
||||
prev: Option<AdjacentPost>,
|
||||
next: Option<AdjacentPost>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct ResponseModel {
|
||||
post: SelectedPost,
|
||||
adjacent: AdjacentPosts,
|
||||
}
|
||||
|
||||
pub async fn handler(
|
||||
State(db_connection): State<PgPool>,
|
||||
Path(identifier): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let post = match if let Ok(id) = identifier.parse::<Uuid>() {
|
||||
select_post_by_uuid(&db_connection, id).await
|
||||
} else {
|
||||
select_post_by_str_id(&db_connection, &identifier).await
|
||||
} {
|
||||
Ok(post) => post,
|
||||
Err(e) => {
|
||||
return match e {
|
||||
sqlx::Error::RowNotFound => err_resp(StatusCode::NOT_FOUND, "no matching post"),
|
||||
_ => err_resp(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let adj_ids = query_as::<_, AdjacentIds>(SELECT_POST_ADJACENT_IDS)
|
||||
.bind(post.id)
|
||||
.fetch_one(&db_connection)
|
||||
.await
|
||||
.unwrap();
|
||||
let adjacent_prev = OptionFuture::from(
|
||||
adj_ids
|
||||
.prev
|
||||
.map(|id| select_post_with_adjacent_model(&db_connection, id)),
|
||||
)
|
||||
.await
|
||||
.flatten();
|
||||
let adjacent_next = OptionFuture::from(
|
||||
adj_ids
|
||||
.next
|
||||
.map(|id| select_post_with_adjacent_model(&db_connection, id)),
|
||||
)
|
||||
.await
|
||||
.flatten();
|
||||
let adjacent = AdjacentPosts {
|
||||
prev: adjacent_prev,
|
||||
next: adjacent_next,
|
||||
};
|
||||
|
||||
let response = ResponseModel { post, adjacent };
|
||||
succ_resp(StatusCode::OK, serde_json::to_value(response).unwrap())
|
||||
}
|
||||
|
||||
async fn select_post_by_uuid(db: &PgPool, id: Uuid) -> Result<SelectedPost, sqlx::Error> {
|
||||
query_as::<_, SelectedPost>(SELECT_POST_BY_UUID)
|
||||
.bind(id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn select_post_by_str_id(db: &PgPool, id: &str) -> Result<SelectedPost, sqlx::Error> {
|
||||
query_as::<_, SelectedPost>(SELECT_POST_BY_STR_ID)
|
||||
.bind(id)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn select_post_with_adjacent_model(db: &PgPool, id: Uuid) -> Option<AdjacentPost> {
|
||||
query_as(SELECT_POST_WITH_ADJACENT_MODEL)
|
||||
.bind(id)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
WITH target_create_date AS (SELECT p.date_created AS date FROM posts p WHERE p.id = $1)
|
||||
SELECT (SELECT p.id
|
||||
FROM posts p
|
||||
WHERE p.date_created < t.date
|
||||
ORDER BY p.date_created DESC
|
||||
LIMIT 1) AS prev,
|
||||
(SELECT p.id
|
||||
FROM posts p
|
||||
WHERE p.date_created > t.date
|
||||
ORDER BY p.date_created
|
||||
LIMIT 1) AS next
|
||||
FROM target_create_date t
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
SELECT p.id,
|
||||
p.identifier,
|
||||
p.title,
|
||||
p.subtitle,
|
||||
p.status,
|
||||
p.date_created,
|
||||
p.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,
|
||||
p.content,
|
||||
(SELECT json_object_agg(m.meta_key, m.meta_value)
|
||||
FROM metadata m
|
||||
WHERE m.pid = p.id) AS metadata
|
||||
FROM posts p
|
||||
JOIN categories c ON c.id = p.category
|
||||
WHERE p.identifier = $1;
|
||||
@@ -1 +1,19 @@
|
||||
SELECT
|
||||
SELECT p.id,
|
||||
p.identifier,
|
||||
p.title,
|
||||
p.subtitle,
|
||||
p.status,
|
||||
p.date_created,
|
||||
p.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,
|
||||
p.content,
|
||||
(SELECT json_object_agg(m.meta_key, m.meta_value)
|
||||
FROM metadata m
|
||||
WHERE m.pid = p.id) AS metadata
|
||||
FROM posts p
|
||||
JOIN categories c ON c.id = p.category
|
||||
WHERE p.id = $1;
|
||||
@@ -0,0 +1,17 @@
|
||||
SELECT p.id,
|
||||
identifier,
|
||||
title,
|
||||
subtitle,
|
||||
date_created,
|
||||
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 = $1
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod get;
|
||||
pub mod get_identifier;
|
||||
pub mod pagination;
|
||||
@@ -4,18 +4,31 @@ use crate::{
|
||||
};
|
||||
|
||||
use axum::{extract::State, http::StatusCode, response::Response};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, json};
|
||||
use sqlx::{FromRow, PgPool, Row, query_as};
|
||||
use sqlx::{FromRow, PgPool, query_as};
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
#[derive(FromRow)]
|
||||
struct DBPaginationRow {
|
||||
page_count: i32,
|
||||
total_count: i64,
|
||||
}
|
||||
|
||||
impl From<DBPaginationRow> for Value {
|
||||
struct PaginationResponse {
|
||||
page_count: i32,
|
||||
total_count: i64,
|
||||
}
|
||||
|
||||
impl From<DBPaginationRow> for PaginationResponse {
|
||||
fn from(value: DBPaginationRow) -> Self {
|
||||
Self {
|
||||
page_count: value.page_count,
|
||||
total_count: value.total_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaginationResponse> for Value {
|
||||
fn from(value: PaginationResponse) -> Self {
|
||||
json!({
|
||||
"page_count": value.page_count,
|
||||
"total_count": value.total_count
|
||||
@@ -36,7 +49,7 @@ pub async fn handler(State(db_connection): State<PgPool>) -> Response<String> {
|
||||
.fetch_one(&db_connection)
|
||||
.await
|
||||
{
|
||||
Ok(res) => succ_resp(StatusCode::OK, res.into()),
|
||||
Err(e) => err_resp(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
Ok(res) => succ_resp(StatusCode::OK, PaginationResponse::from(res).into()),
|
||||
Err(e) => err_resp(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
modules::{
|
||||
Module,
|
||||
blog::{
|
||||
api::{posts, posts::pagination},
|
||||
api::{posts, posts::get, posts::pagination},
|
||||
components::{check_update, consistency_guard},
|
||||
},
|
||||
},
|
||||
@@ -27,11 +27,9 @@ impl Module for Blog {
|
||||
// todo: move resource init to new func
|
||||
// todo: remove debug comment
|
||||
Router::new()
|
||||
// .route("/get_post_list", get(get_post_list))
|
||||
// .route("/posts/pagination", get(...))
|
||||
// .route("/posts", get(...)) // get posts
|
||||
.route("/posts", get(get::handler))
|
||||
.route("/posts/{identifier}", get(posts::get_identifier::handler))
|
||||
.route("/posts/pagination", get(pagination::handler))
|
||||
.route("/posts/{identifier}", get(posts::get::handler))
|
||||
.with_state(db_handler)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ CREATE INDEX idx_categories_name ON categories (name);
|
||||
CREATE INDEX idx_posts_identifier ON posts (identifier);
|
||||
CREATE INDEX idx_posts_status ON posts (status);
|
||||
CREATE INDEX idx_posts_category ON posts (category);
|
||||
CREATE INDEX idx_posts_date_created ON posts (date_created);
|
||||
CREATE INDEX idx_post_tag_post ON post_tag (post);
|
||||
CREATE INDEX idx_post_tag_tag ON post_tag (tag);
|
||||
CREATE INDEX idx_metadata_pid ON metadata (pid);
|
||||
@@ -4,7 +4,7 @@ use axum::{
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
pub fn err_resp(code: StatusCode, err_msg: String) -> Response<String> {
|
||||
pub fn err_resp(code: StatusCode, err_msg: &str) -> Response<String> {
|
||||
let payload = json!({
|
||||
"status": "error",
|
||||
"data": {
|
||||
|
||||
Reference in new issue
Block a user