diff --git a/backend/server/src/modules/blog/api/posts/get.rs b/backend/server/src/modules/blog/api/posts/get.rs index aa3da53..b8c2d4a 100644 --- a/backend/server/src/modules/blog/api/posts/get.rs +++ b/backend/server/src/modules/blog/api/posts/get.rs @@ -12,7 +12,7 @@ use axum::{ use chrono::NaiveDate; use serde::{Deserialize, Serialize}; use serde_json::json; -use sqlx::{query_as, FromRow, PgPool}; +use sqlx::{FromRow, PgPool, query_as}; use uuid::Uuid; #[derive(FromRow, Serialize)] diff --git a/backend/server/src/modules/blog/api/posts/get_identifier.rs b/backend/server/src/modules/blog/api/posts/get_identifier.rs index 05b103b..e377ea7 100644 --- a/backend/server/src/modules/blog/api/posts/get_identifier.rs +++ b/backend/server/src/modules/blog/api/posts/get_identifier.rs @@ -28,7 +28,7 @@ struct SelectedPost { category: String, tags: Vec, content: String, - metadata: Value, + metadata: Option, } #[derive(serde::Serialize, FromRow)] diff --git a/backend/server/src/modules/blog/api/posts/pagination/mod.rs b/backend/server/src/modules/blog/api/posts/pagination/mod.rs index 6bb4d6f..8d9cb6a 100644 --- a/backend/server/src/modules/blog/api/posts/pagination/mod.rs +++ b/backend/server/src/modules/blog/api/posts/pagination/mod.rs @@ -7,19 +7,19 @@ use sqlx::{FromRow, PgPool, query_as}; #[derive(FromRow)] struct DBPaginationRow { page_count: i32, - total_count: i64, + post_count: i64, } struct PaginationResponse { page_count: i32, - total_count: i64, + post_count: i64, } impl From for PaginationResponse { fn from(value: DBPaginationRow) -> Self { Self { page_count: value.page_count, - total_count: value.total_count, + post_count: value.post_count, } } } @@ -28,7 +28,7 @@ impl From for Value { fn from(value: PaginationResponse) -> Self { json!({ "page_count": value.page_count, - "total_count": value.total_count + "post_count": value.post_count }) } } @@ -36,9 +36,9 @@ impl From for Value { pub async fn handler(State(db_connection): State) -> Response { match query_as::<_, DBPaginationRow>( r#" - SELECT + SELECT cast(ceil(count(*) / cast($1 AS FLOAT)) AS INTEGER) AS page_count, - count(*) AS total_count + count(*) AS post_count FROM posts; "#, ) diff --git a/frontend/app/(pages)/blog/BlogPostCard.tsx b/frontend/app/(pages)/blog/BlogPostCard.tsx index 0ca61a8..9e9449d 100644 --- a/frontend/app/(pages)/blog/BlogPostCard.tsx +++ b/frontend/app/(pages)/blog/BlogPostCard.tsx @@ -5,41 +5,42 @@ import Image from 'next/image'; import Link from 'next/link'; import { MdCalendarMonth, MdNorthEast } from 'react-icons/md'; +interface Post { + id: string, + identifier: string, + title: string, + subtitle: string, + tags: string[] + category: string, + date_created: Date, + date_updated: Date | null, + header_img: string | null +} + type Props = { - post: JSON + post: Post } function BlogPostCard({ post }: Props) { - let title = (post as any)['title']; - let sub_title = (post as any)['sub_title']; - // let summary = (post as any)['summary']; - let category = (post as any)['category']; - let tags = (post as any)['tags']; - // let last_update = new Date((post as any)['last_update']); - let first_update = new Date((post as any)['first_update']); - let post_id = (post as any)['post_id']; - let header_img = ((post as any)['header_img'] as string | null); - return ( -
- - { header_img &&
+ { post.header_img &&
{ {
-
+
{ category } -

{ title }

-

{ sub_title }

+ variant={ `solid` }>{ post.category } +

{ post.title }

+

{ post.subtitle }

@@ -63,7 +64,7 @@ function BlogPostCard({ post }: Props) {
    { - tags.map((tag: string, key: any) => ( + post.tags.map((tag: string, key: any) => (
  • { tag }
  • @@ -74,7 +75,7 @@ function BlogPostCard({ post }: Props) {

    - { first_update.toLocaleString('default', { + { post.date_created.toLocaleString('default', { month: `long`, day: `numeric`, year: `numeric` diff --git a/frontend/app/(pages)/blog/PostList.tsx b/frontend/app/(pages)/blog/PostList.tsx index c815d5c..63f7d0c 100644 --- a/frontend/app/(pages)/blog/PostList.tsx +++ b/frontend/app/(pages)/blog/PostList.tsx @@ -1,21 +1,21 @@ 'use server'; import React from 'react'; -import { fetchNocturneApi } from '@/app/(pages)/blog/api'; import BlogPostCard from '@/app/(pages)/blog/BlogPostCard'; -import { Card, CardBody } from '@nextui-org/card'; import * as motion from 'motion/react-client'; +import { fetchPostList } from '@/src/api/blog/post'; const PostList = async ({ page }: { page: number }) => { try { - const res = await fetchNocturneApi(`/blog/get_post_list?page=${ page }`); - const posts = await res.json(); + const postListResponse = await fetchPostList(page); + + const posts = postListResponse.data.posts; return ( // blog list

      - { posts.map((post: JSON, key: number) => ( + { posts.map((post, key: number) => (
    • { ); } catch (e: any) { return ( - // todo: use Error.tsx - - - Fatal: { e.toString() } - - ); + <> +
      +
      +

      + { 'Unexpected Error:' } +

      +

      + { e.message } +

      +
      +
      + + ); + } }; export default PostList; diff --git a/frontend/app/(pages)/blog/api.ts b/frontend/app/(pages)/blog/api.ts deleted file mode 100644 index 9b4c73e..0000000 --- a/frontend/app/(pages)/blog/api.ts +++ /dev/null @@ -1,21 +0,0 @@ -const LOCAL_ENDPOINT = process.env.API_LOCAL_URL; -const REMOTE_ENDPOINT = process.env.API_REMOTE_URL; - -const NOCTURNE_ENDPOINT = ((isServerSide = true) => { - if (process.env.NODE_ENV === 'production') { - if (isServerSide) { - return LOCAL_ENDPOINT; - } else { - return REMOTE_ENDPOINT; - } - } else { - return LOCAL_ENDPOINT; - } -}); - -export async function fetchNocturneApi(path: string, isServerSideRequest = true) { - return await fetch(`${ NOCTURNE_ENDPOINT(isServerSideRequest) }${ path }`, { - cache: 'no-cache' - }); -} - diff --git a/frontend/app/(pages)/blog/page.tsx b/frontend/app/(pages)/blog/page.tsx index f6b4939..ff4b6a4 100644 --- a/frontend/app/(pages)/blog/page.tsx +++ b/frontend/app/(pages)/blog/page.tsx @@ -1,6 +1,7 @@ import PostList from '@/app/(pages)/blog/PostList'; import Pagination from '@/app/(pages)/blog/Pagination'; -import { fetchNocturneApi } from '@/app/(pages)/blog/api'; +import { fetchPostListPagination } from '@/src/api/blog/post'; + async function Blog({ searchParams @@ -10,15 +11,34 @@ async function Blog({ const parsePage = Number((await searchParams)?.page); const currPage = parsePage > 0 ? parsePage : 1; - const ret = await fetchNocturneApi(`/blog/get_page_count`); - const pageCount = (await ret.json() as any)['res'] as number; - return ( -
      - - -
      - ); + + try { + const pagination = await fetchPostListPagination(); + const pageCount = pagination.data.page_count; + + return ( +
      + + +
      + ); + } catch (e: any) { + return ( +
      +
      +

      + { 'Unexpected Error [loading pagination]:' } +

      +

      + { e.message } +

      +
      +
      + ); + } + + } export default Blog; diff --git a/frontend/app/(pages)/blog/post/[id]/Footer.tsx b/frontend/app/(pages)/blog/post/[id]/Footer.tsx index e0ac2b5..f088d14 100644 --- a/frontend/app/(pages)/blog/post/[id]/Footer.tsx +++ b/frontend/app/(pages)/blog/post/[id]/Footer.tsx @@ -3,27 +3,28 @@ import MinifyPostCard from '@/app/(pages)/blog/post/[id]/MinifyPostCard'; import Link from 'next/link'; import { FaCreativeCommons } from 'react-icons/fa6'; +interface AdjacentPosts { + prev: any; + next: any; +} + type Props = { - // post: any - post_id: string; - adjacent_posts: any - perm_link: string | undefined; - author: string | undefined; - author_link: string | undefined; + adjacent_posts: AdjacentPosts + perm_link: string; + author: string; + author_link: string; first_update: string; - last_update: string | undefined; + last_update: string | null; cc: string | undefined; title: string; } const Footer = (props: Props) => { - const { adjacent_posts, perm_link, post_id, first_update, last_update, title } = props; + const { adjacent_posts, perm_link, author_link, author, first_update, last_update, title } = props; const prev_post = adjacent_posts.prev; const next_post = adjacent_posts.next; - const link = perm_link ?? `https://lumither.com/blog/post/${ post_id }`; - const author = props.author ?? 'Tao'; - const author_link = props.author_link ?? '/about/me'; + const link = perm_link; const post_date = new Date(first_update); const update_date: Date | undefined = last_update ? new Date(last_update) : undefined; const cc = props.cc ?? 'by-nc-sa'; diff --git a/frontend/app/(pages)/blog/post/[id]/Headers.tsx b/frontend/app/(pages)/blog/post/[id]/Headers.tsx index 7c83e51..a295173 100644 --- a/frontend/app/(pages)/blog/post/[id]/Headers.tsx +++ b/frontend/app/(pages)/blog/post/[id]/Headers.tsx @@ -3,14 +3,9 @@ import Image from 'next/image'; import { Chip } from '@nextui-org/react'; import { MdCalendarMonth } from 'react-icons/md'; -// type Props = { -// post: any -// }; - type Props = { title: string; subtitle: string; - summary: string | undefined; category: string; tags: string[]; first_update: string; diff --git a/frontend/app/(pages)/blog/post/[id]/MinifyPostCard.tsx b/frontend/app/(pages)/blog/post/[id]/MinifyPostCard.tsx index ac9212f..4352659 100644 --- a/frontend/app/(pages)/blog/post/[id]/MinifyPostCard.tsx +++ b/frontend/app/(pages)/blog/post/[id]/MinifyPostCard.tsx @@ -36,7 +36,7 @@ const MinifyPostCard = (props: Props) => { className="absolute transition-colors bg-zinc-300/80 dark:bg-gray-600/25 dark:backdrop-brightness-75 bottom-0 justify-between rounded-none">

      { post['title'] }

      -

      { post['sub_title'] }

      +

      { post.subtitle }

    @@ -46,7 +46,7 @@ const MinifyPostCard = (props: Props) => {

    { post['title'] }

    -

    { post['sub_title'] }

    +

    { post.subtitle }

    } diff --git a/frontend/app/(pages)/blog/post/[id]/PostCard.tsx b/frontend/app/(pages)/blog/post/[id]/PostCard.tsx index 27b0253..97f9437 100644 --- a/frontend/app/(pages)/blog/post/[id]/PostCard.tsx +++ b/frontend/app/(pages)/blog/post/[id]/PostCard.tsx @@ -3,25 +3,28 @@ import { Card, CardBody } from '@nextui-org/card'; import Headers from '@/app/(pages)/blog/post/[id]/Headers'; import Footer from '@/app/(pages)/blog/post/[id]/Footer'; import MarkdownRenderer from '@/app/(pages)/public/MarkdownRenderer'; +import { FetchPostResponseData } from '@/src/api/blog/post'; type Props = { - post: any + post: FetchPostResponseData } async function PostCard(props: Props) { - let post = props.post; + const post = props.post.post; + const metadata: any | null = post.metadata; + const adj = props.post.adjacent; + return (
    {/* card headers */ } {/* card body */ } @@ -36,14 +39,13 @@ async function PostCard(props: Props) { {/* card footer */ }