Random posts route

This commit is contained in:
Max 2024-12-04 23:01:05 +03:00
parent 1ab8022b95
commit a08e068030
12 changed files with 301 additions and 30 deletions

View File

@ -12,6 +12,7 @@
"@radix-ui/react-form": "^0.1.0", "@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.1", "@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-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/themes": "^3.1.3", "@radix-ui/themes": "^3.1.3",
@ -2044,17 +2045,17 @@
} }
}, },
"node_modules/@radix-ui/react-scroll-area": { "node_modules/@radix-ui/react-scroll-area": {
"version": "1.1.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz",
"integrity": "sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==", "integrity": "sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@radix-ui/number": "1.1.0", "@radix-ui/number": "1.1.0",
"@radix-ui/primitive": "1.1.0", "@radix-ui/primitive": "1.1.0",
"@radix-ui/react-compose-refs": "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-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-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "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": { "node_modules/@radix-ui/react-select": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", "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": { "node_modules/@radix-ui/themes/node_modules/@radix-ui/react-tooltip": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.1.tgz",

View File

@ -14,6 +14,7 @@
"@radix-ui/react-form": "^0.1.0", "@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-navigation-menu": "^1.2.1", "@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-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/themes": "^3.1.3", "@radix-ui/themes": "^3.1.3",

View File

@ -0,0 +1,8 @@
export type GetRandomPostsRow = {
post_id: string;
// blog_id: number;
user_id: string;
title: string;
// created_at: Date;
}

View File

@ -14,7 +14,7 @@ type TArticleViewer = {
export default function ArticleViewer(props: TArticleViewer) { export default function ArticleViewer(props: TArticleViewer) {
let queryParams = useParams(); let queryParams = useParams();
const user = useAtomValue(userAtom) const user = useAtomValue(userAtom);
const { data, isPending } = useQuery({ const { data, isPending } = useQuery({
queryKey: [`post_${queryParams["postId"]}`], queryKey: [`post_${queryParams["postId"]}`],
@ -25,31 +25,32 @@ export default function ArticleViewer(props: TArticleViewer) {
return response.data; return response.data;
}, },
}) });
if (isPending) return <SkeletonPostLoader />; if (isPending) return <SkeletonPostLoader />;
return ( return (
<> <>
<div className="ql-snow"> <Container size={"3"}>
<Container size={"2"} className="mt-4"> <div className="ql-snow ql-editor">
<Flex direction={"column"}> <Container size={"2"} className="mt-4">
<Text className="mb-2" as="div" size={"9"}> <Flex direction={"column"}>
{data.title} <Text className="mb-2" as="div" size={"9"}>
</Text> {data.title}
<Flex className="mt-4 mb-2"> </Text>
<div hidden={data.user_id != user?.id}> <Flex className="mt-4 mb-2">
<ChangePostButton <div hidden={data.user_id != user?.id}>
postId={queryParams["postId"] || ""} <ChangePostButton
/> postId={queryParams["postId"] || ""}
</div> />
</div>
</Flex>
</Flex> </Flex>
</Flex> <Separator size={"4"} className="mb-2" />
<Separator size={"4"} className="mb-2" /> <Interweave content={data.content} />
<Interweave content={data.content} /> </Container>
</Container> </div>
</div> </Container>
</> </>
); );
} }

View File

@ -12,6 +12,10 @@ const RETRY_INTERVAL_IN_SECONDS = 1;
const SECONDS_IN_MINUTE = 60; const SECONDS_IN_MINUTE = 60;
const MILLS_IN_SECOND = 1000; 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() { export default function MainPage() {
const setUserData = useSetAtom(userAtom); const setUserData = useSetAtom(userAtom);
@ -53,11 +57,14 @@ export default function MainPage() {
<Spinner size={"3"} /> <Spinner size={"3"} />
</div> </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]"> <Box flexGrow={"1"} className="flex-[1]">
<NavBar /> <NavBar />
</Box> </Box>
<Box flexGrow={"100"} className="flex flex-col overflow-auto"> <Box flexGrow={"100"} className="flex overflow-hidden flex-">
<Outlet /> <Outlet />
</Box> </Box>
</Flex> </Flex>

View 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>
);
}

View 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>
</>
);
}

View File

@ -11,6 +11,7 @@ import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage"; import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
import MainPage from "../Pages/MainPage/MainPage"; import MainPage from "../Pages/MainPage/MainPage";
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage"; import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
function ErrorBoundary() { function ErrorBoundary() {
let error = useRouteError(); let error = useRouteError();
@ -22,12 +23,12 @@ function ErrorBoundary() {
export const routes = createRoutesFromElements( export const routes = createRoutesFromElements(
<> <>
<Route path="/" errorElement={<ErrorBoundary />} element={<MainPage />}> <Route path="/" errorElement={<ErrorBoundary />} element={<MainPage />}>
<Route index element={<Text size={"5"}>Cringer path</Text>} /> <Route index element={<RandomPostsPage />} />
<Route <Route
path="/a?/c" path="/a?/c"
element={ element={
<Text weight={"regular"}>Cringer path, but this a</Text> <Text weight={"regular"}>This page is yet to be created</Text>
} }
/> />

View File

@ -146,6 +146,47 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
return items, nil 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 const updatePostBlogId = `-- name: UpdatePostBlogId :exec
UPDATE public.posts UPDATE public.posts
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP SET blog_id=$2, updated_at=CURRENT_TIMESTAMP

View File

@ -34,3 +34,9 @@ UPDATE public.posts
SET blog_id=$2, updated_at=CURRENT_TIMESTAMP SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $1 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;

View 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)
}

View File

@ -65,6 +65,12 @@ func SetupRotes(g *gin.Engine) error {
"posts/:post-id", "posts/:post-id",
postsRoutes.GetPost, postsRoutes.GetPost,
) )
postsGroup.GET(
"posts/random",
postsRoutes.GetRandomPost,
)
postsGroup.PUT( postsGroup.PUT(
"posts/:post-id", "posts/:post-id",
postsRoutes.UpdatePost, postsRoutes.UpdatePost,