diff --git a/enshi/package-lock.json b/enshi/package-lock.json index 6cdc4e5..70ebfc5 100644 --- a/enshi/package-lock.json +++ b/enshi/package-lock.json @@ -33,6 +33,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^15.0.1", + "react-intersection-observer": "^9.15.1", "react-quill": "^2.0.0", "react-router-dom": "^6.26.2" }, @@ -6486,6 +6487,21 @@ } } }, + "node_modules/react-intersection-observer": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.15.1.tgz", + "integrity": "sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==", + "license": "MIT", + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/enshi/package.json b/enshi/package.json index b5cb8c4..dcc5e4b 100644 --- a/enshi/package.json +++ b/enshi/package.json @@ -36,6 +36,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-i18next": "^15.0.1", + "react-intersection-observer": "^9.15.1", "react-quill": "^2.0.0", "react-router-dom": "^6.26.2" }, diff --git a/enshi/src/@types/PostTypes.ts b/enshi/src/@types/PostTypes.ts index 0873cc1..d744ea8 100644 --- a/enshi/src/@types/PostTypes.ts +++ b/enshi/src/@types/PostTypes.ts @@ -4,10 +4,24 @@ export type GetRandomPostsRow = { user_id: string; title: string; // created_at: Date; -} - +}; export type TPostData = { title: string; content: string; -}; \ No newline at end of file +}; + +export type Post = { + blog_id: string | null; + created_at: string; // ISO 8601 date string + post_id: string; + title: string; + user_id: string; +}; + +export type SelectedPostsResponse = { + selected_posts: Post[]; + has_next_page: boolean; + next_page_index: number; + prev_page_index: number; +}; diff --git a/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx b/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx index 605e04f..0d3e7f7 100644 --- a/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx +++ b/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx @@ -1,67 +1,91 @@ -import * as ScrollArea from "@radix-ui/react-scroll-area"; -import { Container, Flex, Heading, Separator } from "@radix-ui/themes"; -import { useQuery } from "@tanstack/react-query"; +import { + Box, + Container, + Flex, + Heading, + ScrollArea, + Separator, +} from "@radix-ui/themes"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { GetRandomPostsRow } from "../../@types/PostTypes"; +import { useInView } from "react-intersection-observer"; +import { SelectedPostsResponse } from "../../@types/PostTypes"; import { axiosLocalhost } from "../../api/axios/axios"; import PostCard from "./PostCard/PostCard"; -const LIMIT = 10; +const LIMIT = 3; export default function RandomPostsPage() { - const {t} = useTranslation() + const { t } = useTranslation(); - const { data, refetch } = useQuery({ - queryKey: ["random_posts_key"], - queryFn: async () => { - try { - const response = await axiosLocalhost.get( - `/posts/random?limit=${LIMIT}` - ); + const [ref, inView] = useInView(); - return response.data as GetRandomPostsRow[]; - } catch (error) { - console.log(`Something went wrong`); - } + const { data, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ + queryKey: [`random_post_inf`], + queryFn: async ({ pageParam }): Promise => { + const response = await axiosLocalhost.get( + `/posts/random?limit=${LIMIT}&offset=${pageParam}` + ); - return []; + return response.data as SelectedPostsResponse; }, + initialPageParam: 0, + getPreviousPageParam: (lastPage) => + lastPage.prev_page_index < 0 ? undefined : lastPage.prev_page_index, + getNextPageParam: (lastPage) => + lastPage.next_page_index < 0 ? undefined : lastPage.next_page_index, }); + // const { data, refetch } = useQuery({ + // queryKey: ["random_posts_key"], + // queryFn: async () => { + // try { + // const response = await axiosLocalhost.get( + // `/posts/random?limit=${LIMIT}&offset=0` + // ); + + // return response.data as GetRandomPostsRow[]; + // } catch (error) { + // console.log(`Something went wrong`); + // } + + // return []; + // }, + // }); + + useEffect(() => { + if (inView) { + console.log(`Loading more posts...`); + if (hasNextPage) fetchNextPage(); + } + }, [inView]); + return ( <> - - - {t("discover")} - + + + {t("discover")} + - - - - - {data?.map((post, i) => { - return ( - - - - ); - })} - - - - - {/* - - */} - {/* */} - - + + + {data?.pages.map((post, i) => { + return ( + <> + {post.selected_posts.map((post) => { + return ( + + + + ); + })} + + ); + })} + qqqqqqqqqqqq + + ); } diff --git a/enshi_back/db/go_queries/posts_queries.sql.go b/enshi_back/db/go_queries/posts_queries.sql.go index 2db5075..bb44bee 100644 --- a/enshi_back/db/go_queries/posts_queries.sql.go +++ b/enshi_back/db/go_queries/posts_queries.sql.go @@ -146,45 +146,70 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e return items, nil } -const getRandomPosts = `-- name: GetRandomPosts :many -SELECT post_id, blog_id, user_id, title, created_at -FROM public.posts -ORDER BY RANDOM() -LIMIT $1 +const getRandomPosts = `-- name: GetRandomPosts :one +WITH all_posts AS ( + SELECT + COUNT(*) AS post_count + FROM + public.posts +), +filtered_posts AS ( + SELECT + ARRAY( + SELECT + json_build_object( + 'post_id', post_id::text, 'blog_id', blog_id::text, + 'user_id', user_id::text, 'title', title, + 'created_at', created_at + ) + FROM + public.posts + ORDER BY + created_at DESC + LIMIT $1 OFFSET $3 + ) as selected_posts +) +SELECT + fp.selected_posts, + (ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page, + case + when (ap.post_count - ($2 + 1) * $1)::int > 0 + then $2 + 1 + else -1 + end as next_page_index, + case + when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count + then $2 - 1 + else -1 + end as prev_page_index +FROM + filtered_posts fp, + all_posts ap ` -type GetRandomPostsRow struct { - PostID int64 `json:"post_id"` - BlogID pgtype.Int8 `json:"blog_id"` - UserID int64 `json:"user_id"` - Title pgtype.Text `json:"title"` - CreatedAt pgtype.Timestamp `json:"created_at"` +type GetRandomPostsParams struct { + Column1 interface{} `json:"column_1"` + Column2 interface{} `json:"column_2"` + Offset int32 `json:"offset"` } -func (q *Queries) GetRandomPosts(ctx context.Context, limit int32) ([]GetRandomPostsRow, error) { - rows, err := q.db.Query(ctx, getRandomPosts, limit) - if err != nil { - return nil, err - } - defer rows.Close() - var items []GetRandomPostsRow - for rows.Next() { - var i GetRandomPostsRow - if err := rows.Scan( - &i.PostID, - &i.BlogID, - &i.UserID, - &i.Title, - &i.CreatedAt, - ); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil +type GetRandomPostsRow struct { + SelectedPosts interface{} `json:"selected_posts"` + HasNextPage bool `json:"has_next_page"` + NextPageIndex int32 `json:"next_page_index"` + PrevPageIndex int32 `json:"prev_page_index"` +} + +func (q *Queries) GetRandomPosts(ctx context.Context, arg GetRandomPostsParams) (GetRandomPostsRow, error) { + row := q.db.QueryRow(ctx, getRandomPosts, arg.Column1, arg.Column2, arg.Offset) + var i GetRandomPostsRow + err := row.Scan( + &i.SelectedPosts, + &i.HasNextPage, + &i.NextPageIndex, + &i.PrevPageIndex, + ) + return i, err } const updatePostBlogId = `-- name: UpdatePostBlogId :exec diff --git a/enshi_back/db/queries/posts_queries.sql b/enshi_back/db/queries/posts_queries.sql index 9f22b8f..49954e0 100644 --- a/enshi_back/db/queries/posts_queries.sql +++ b/enshi_back/db/queries/posts_queries.sql @@ -35,8 +35,42 @@ SET blog_id=$2, updated_at=CURRENT_TIMESTAMP WHERE post_id = $1 RETURNING *; --- name: GetRandomPosts :many -SELECT post_id, blog_id, user_id, title, created_at -FROM public.posts -ORDER BY RANDOM() -LIMIT $1; \ No newline at end of file +-- name: GetRandomPosts :one +WITH all_posts AS ( + SELECT + COUNT(*) AS post_count + FROM + public.posts +), +filtered_posts AS ( + SELECT + ARRAY( + SELECT + json_build_object( + 'post_id', post_id::text, 'blog_id', blog_id::text, + 'user_id', user_id::text, 'title', title, + 'created_at', created_at + ) + FROM + public.posts + ORDER BY + created_at DESC + LIMIT $1 OFFSET $3 + ) as selected_posts +) +SELECT + fp.selected_posts, + (ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page, + case + when (ap.post_count - ($2 + 1) * $1)::int > 0 + then $2 + 1 + else -1 + end as next_page_index, + case + when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count + then $2 - 1 + else -1 + end as prev_page_index +FROM + filtered_posts fp, + all_posts ap; diff --git a/enshi_back/routes/postsRoutes/getRandomPosts.go b/enshi_back/routes/postsRoutes/getRandomPosts.go index f8f2139..db021b8 100644 --- a/enshi_back/routes/postsRoutes/getRandomPosts.go +++ b/enshi_back/routes/postsRoutes/getRandomPosts.go @@ -19,25 +19,38 @@ func GetRandomPost(c *gin.Context) { return } - postsData, err := - db_repo.New(db_connection.Dbx). - GetRandomPosts(context.Background(), int32(limit)) + offset, err := strconv.Atoi(c.DefaultQuery("offset", "0")) if err != nil { rest_api_stuff.InternalErrorAnswer(c, err) return } - result := make([]any, 0) - - for _, post := range postsData { - result = append(result, gin.H{ - "post_id": strconv.Itoa(int(post.PostID)), - "title": post.Title, - "user_id": strconv.Itoa(int(post.UserID)), - }) + params := db_repo.GetRandomPostsParams{ + Column1: int32(limit), + Column2: int32(offset), + Offset: int32(offset * limit), } - c.IndentedJSON(http.StatusOK, result) + postsData, err := + db_repo.New(db_connection.Dbx). + GetRandomPosts(context.Background(), params) + + if err != nil { + rest_api_stuff.InternalErrorAnswer(c, err) + return + } + + // result := make([]any, 0) + + // for _, post := range postsData { + // result = append(result, gin.H{ + // "post_id": strconv.Itoa(int(post.PostID)), + // "title": post.Title, + // "user_id": strconv.Itoa(int(post.UserID)), + // }) + // } + + c.IndentedJSON(http.StatusOK, postsData) }