Random posts route
This commit is contained in:
parent
1ab8022b95
commit
a08e068030
81
enshi/package-lock.json
generated
81
enshi/package-lock.json
generated
@ -12,6 +12,7 @@
|
||||
"@radix-ui/react-form": "^0.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.1",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@radix-ui/themes": "^3.1.3",
|
||||
@ -2044,17 +2045,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz",
|
||||
"integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz",
|
||||
"integrity": "sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
@ -2074,6 +2075,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz",
|
||||
"integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-select": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz",
|
||||
@ -2678,6 +2718,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/themes/node_modules/@radix-ui/react-scroll-area": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz",
|
||||
"integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.0",
|
||||
"@radix-ui/primitive": "1.1.0",
|
||||
"@radix-ui/react-compose-refs": "1.1.0",
|
||||
"@radix-ui/react-context": "1.1.0",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/themes/node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.1.tgz",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"@radix-ui/react-form": "^0.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
||||
"@radix-ui/react-scroll-area": "^1.2.1",
|
||||
"@radix-ui/react-toast": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@radix-ui/themes": "^3.1.3",
|
||||
|
||||
8
enshi/src/@types/PostTypes.ts
Normal file
8
enshi/src/@types/PostTypes.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export type GetRandomPostsRow = {
|
||||
post_id: string;
|
||||
// blog_id: number;
|
||||
user_id: string;
|
||||
title: string;
|
||||
// created_at: Date;
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ type TArticleViewer = {
|
||||
|
||||
export default function ArticleViewer(props: TArticleViewer) {
|
||||
let queryParams = useParams();
|
||||
const user = useAtomValue(userAtom)
|
||||
const user = useAtomValue(userAtom);
|
||||
|
||||
const { data, isPending } = useQuery({
|
||||
queryKey: [`post_${queryParams["postId"]}`],
|
||||
@ -25,31 +25,32 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
|
||||
return response.data;
|
||||
},
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
if (isPending) return <SkeletonPostLoader />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ql-snow">
|
||||
<Container size={"2"} className="mt-4">
|
||||
<Flex direction={"column"}>
|
||||
<Text className="mb-2" as="div" size={"9"}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Flex className="mt-4 mb-2">
|
||||
<div hidden={data.user_id != user?.id}>
|
||||
<ChangePostButton
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
</div>
|
||||
<Container size={"3"}>
|
||||
<div className="ql-snow ql-editor">
|
||||
<Container size={"2"} className="mt-4">
|
||||
<Flex direction={"column"}>
|
||||
<Text className="mb-2" as="div" size={"9"}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Flex className="mt-4 mb-2">
|
||||
<div hidden={data.user_id != user?.id}>
|
||||
<ChangePostButton
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Separator size={"4"} className="mb-2" />
|
||||
<Interweave content={data.content} />
|
||||
</Container>
|
||||
</div>
|
||||
<Separator size={"4"} className="mb-2" />
|
||||
<Interweave content={data.content} />
|
||||
</Container>
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -12,6 +12,10 @@ const RETRY_INTERVAL_IN_SECONDS = 1;
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
const MILLS_IN_SECOND = 1000;
|
||||
|
||||
const TAGS = Array.from({ length: 50 }).map(
|
||||
(_, i, a) => `v1.2.0-beta.${a.length - i}`
|
||||
);
|
||||
|
||||
export default function MainPage() {
|
||||
const setUserData = useSetAtom(userAtom);
|
||||
|
||||
@ -53,11 +57,14 @@ export default function MainPage() {
|
||||
<Spinner size={"3"} />
|
||||
</div>
|
||||
) : (
|
||||
<Flex direction={"column"} className="min-h-[100vh] max-h-[100vh]">
|
||||
<Flex
|
||||
direction={"column"}
|
||||
className="min-h-[100vh] max-h-[100vh] overflow-hidden"
|
||||
>
|
||||
<Box flexGrow={"1"} className="flex-[1]">
|
||||
<NavBar />
|
||||
</Box>
|
||||
<Box flexGrow={"100"} className="flex flex-col overflow-auto">
|
||||
<Box flexGrow={"100"} className="flex overflow-hidden flex-">
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
30
enshi/src/Pages/RandomPostsPage/PostCard/PostCard.tsx
Normal file
30
enshi/src/Pages/RandomPostsPage/PostCard/PostCard.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { ImageIcon } from "@radix-ui/react-icons";
|
||||
import { Box, Card, Heading } from "@radix-ui/themes";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { GetRandomPostsRow } from "../../../@types/PostTypes";
|
||||
|
||||
type TPostCard = {
|
||||
post: GetRandomPostsRow;
|
||||
};
|
||||
|
||||
export default function PostCard({ post }: TPostCard) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const clickHandler = () => {
|
||||
navigate(`/posts/${post.post_id.toString()}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="h-32 mb-4" onClick={clickHandler}>
|
||||
<Box className="flex size-full">
|
||||
<Box>
|
||||
<ImageIcon className="w-full h-full" />
|
||||
</Box>
|
||||
|
||||
<Box className="px-4 pt-2">
|
||||
<Heading>{post.title}</Heading>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
56
enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx
Normal file
56
enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { Container } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { GetRandomPostsRow } from "../../@types/PostTypes";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import PostCard from "./PostCard/PostCard";
|
||||
|
||||
const LIMIT = 10;
|
||||
|
||||
export default function RandomPostsPage() {
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ["random_posts_key"],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const response = await axiosLocalhost.get(
|
||||
`/posts/random?limit=${LIMIT}`
|
||||
);
|
||||
|
||||
return response.data as GetRandomPostsRow[];
|
||||
} catch (error) {
|
||||
console.log(`Something went wrong`);
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollArea.Root className="w-full overflow-hidden grow-1">
|
||||
<ScrollArea.Viewport className="rounded size-full">
|
||||
{data?.map((post, i) => {
|
||||
return (
|
||||
<Container size={"3"} key={`post${i}`}>
|
||||
<PostCard post={post} />
|
||||
</Container>
|
||||
);
|
||||
})}
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex touch-none select-none bg-blackA3 p-0.5 transition-colors duration-[160ms] ease-out hover:bg-blackA5 data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-mauve10 before:absolute before:left-1/2 before:top-1/2 before:size-full before:min-h-11 before:min-w-11 before:-translate-x-1/2 before:-translate-y-1/2" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Scrollbar
|
||||
className="flex touch-none select-none bg-blackA3 p-0.5 transition-colors duration-[160ms] ease-out hover:bg-blackA5 data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-mauve10 before:absolute before:left-1/2 before:top-1/2 before:size-full before:min-h-[44px] before:min-w-[44px] before:-translate-x-1/2 before:-translate-y-1/2" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Corner className="bg-blackA5" />
|
||||
</ScrollArea.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -11,6 +11,7 @@ import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
|
||||
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
||||
import MainPage from "../Pages/MainPage/MainPage";
|
||||
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
|
||||
import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
|
||||
|
||||
function ErrorBoundary() {
|
||||
let error = useRouteError();
|
||||
@ -22,12 +23,12 @@ function ErrorBoundary() {
|
||||
export const routes = createRoutesFromElements(
|
||||
<>
|
||||
<Route path="/" errorElement={<ErrorBoundary />} element={<MainPage />}>
|
||||
<Route index element={<Text size={"5"}>Cringer path</Text>} />
|
||||
<Route index element={<RandomPostsPage />} />
|
||||
|
||||
<Route
|
||||
path="/a?/c"
|
||||
element={
|
||||
<Text weight={"regular"}>Cringer path, but this a</Text>
|
||||
<Text weight={"regular"}>This page is yet to be created</Text>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@ -146,6 +146,47 @@ 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
|
||||
`
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const updatePostBlogId = `-- name: UpdatePostBlogId :exec
|
||||
UPDATE public.posts
|
||||
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
|
||||
|
||||
@ -33,4 +33,10 @@ WHERE post_id=$1;
|
||||
UPDATE public.posts
|
||||
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE post_id = $1
|
||||
RETURNING *;
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetRandomPosts :many
|
||||
SELECT post_id, blog_id, user_id, title, created_at
|
||||
FROM public.posts
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1;
|
||||
43
enshi_back/routes/postsRoutes/getRandomPosts.go
Normal file
43
enshi_back/routes/postsRoutes/getRandomPosts.go
Normal file
@ -0,0 +1,43 @@
|
||||
package postsRoutes
|
||||
|
||||
import (
|
||||
"context"
|
||||
rest_api_stuff "enshi/REST_API_stuff"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetRandomPost(c *gin.Context) {
|
||||
limit, err := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
postsData, err :=
|
||||
db_repo.New(db_connection.Dbx).
|
||||
GetRandomPosts(context.Background(), int32(limit))
|
||||
|
||||
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, result)
|
||||
|
||||
}
|
||||
@ -65,6 +65,12 @@ func SetupRotes(g *gin.Engine) error {
|
||||
"posts/:post-id",
|
||||
postsRoutes.GetPost,
|
||||
)
|
||||
|
||||
postsGroup.GET(
|
||||
"posts/random",
|
||||
postsRoutes.GetRandomPost,
|
||||
)
|
||||
|
||||
postsGroup.PUT(
|
||||
"posts/:post-id",
|
||||
postsRoutes.UpdatePost,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user