Votes for post
This commit is contained in:
parent
a08e068030
commit
1f7d95a4ff
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,5 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"godotenv"
|
||||
"downvotes",
|
||||
"godotenv",
|
||||
"upvotes"
|
||||
]
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { Container, Flex, Separator, Text } from "@radix-ui/themes";
|
||||
import { Box, Container, Flex, Separator, Text } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Interweave } from "interweave";
|
||||
import { useAtomValue } from "jotai";
|
||||
@ -7,6 +7,8 @@ import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import { userAtom } from "../../AtomStore/AtomStore";
|
||||
import ChangePostButton from "./ChangePostButton/ChangePostButton";
|
||||
import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader";
|
||||
import VoteButton, { DOWNVOTE, UPVOTE } from "./VoteButton/VoteButton";
|
||||
import VoteCounter from "./VoteCounter/VoteCounter";
|
||||
|
||||
type TArticleViewer = {
|
||||
htmlToParse?: string;
|
||||
@ -38,12 +40,26 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
<Text className="mb-2" as="div" size={"9"}>
|
||||
{data.title}
|
||||
</Text>
|
||||
<Flex className="mt-4 mb-2">
|
||||
<div hidden={data.user_id != user?.id}>
|
||||
<Flex gap={"3"} className="mt-4 mb-2">
|
||||
<Flex gap={"1"}>
|
||||
<VoteButton
|
||||
vote={UPVOTE}
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
|
||||
<VoteCounter postId={queryParams["postId"] || ""} />
|
||||
|
||||
<VoteButton
|
||||
vote={DOWNVOTE}
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Box hidden={data.user_id != user?.id}>
|
||||
<ChangePostButton
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Separator size={"4"} className="mb-2" />
|
||||
|
||||
65
enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx
Normal file
65
enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons";
|
||||
import { IconButton } from "@radix-ui/themes";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||
|
||||
export const UPVOTE = true;
|
||||
export const DOWNVOTE = false;
|
||||
|
||||
type TVoteButton = {
|
||||
postId: string;
|
||||
vote: boolean;
|
||||
};
|
||||
|
||||
export default function VoteButton(props: TVoteButton) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: [props.vote + "voteCheck"],
|
||||
queryFn: async () => {
|
||||
const response = await axiosLocalhost.get(
|
||||
`post-vote/${props.postId}`
|
||||
);
|
||||
|
||||
return (response.data?.vote as boolean) === props.vote || false;
|
||||
},
|
||||
gcTime: 0,
|
||||
});
|
||||
|
||||
const voteMutation = useMutation({
|
||||
mutationKey: [`voteMutation${props.vote}`],
|
||||
onMutate: async () => {
|
||||
queryClient.cancelQueries({ queryKey: [props.vote + "voteCheck"] });
|
||||
|
||||
queryClient.setQueryData([props.vote + "voteCheck"], true);
|
||||
queryClient.setQueryData([!props.vote + "voteCheck"], false);
|
||||
},
|
||||
mutationFn: async () => {
|
||||
await axiosLocalhost.post(`post-votes/${props.postId}`, {
|
||||
vote: props.vote,
|
||||
});
|
||||
},
|
||||
onSuccess: () => {},
|
||||
onError: () => {
|
||||
queryClient.setQueryData([props.vote + "voteCheck"], false);
|
||||
},
|
||||
onSettled: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [props.vote + "voteCheck"],
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["post_vote_counter"],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
variant={data ? "solid" : "outline"}
|
||||
size={"1"}
|
||||
onClick={() => voteMutation.mutate()}
|
||||
>
|
||||
{props.vote ? <DoubleArrowUpIcon /> : <DoubleArrowDownIcon />}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
import { Box, Skeleton } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||
|
||||
type TVoteCounter = {
|
||||
postId: string;
|
||||
};
|
||||
|
||||
export default function VoteCounter(props: TVoteCounter) {
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["post_vote_counter"],
|
||||
queryFn: async () => {
|
||||
const response = await axiosLocalhost.get(
|
||||
`post-votes/${props.postId}`
|
||||
);
|
||||
return response.data as { upvotes: number; downvotes: number };
|
||||
},
|
||||
});
|
||||
|
||||
const calculateRating = (upvotes: number, downvotes: number) => {
|
||||
return upvotes + (-downvotes)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton>
|
||||
{calculateRating(0, 0)}
|
||||
</Skeleton>
|
||||
}
|
||||
|
||||
return <Box>
|
||||
{calculateRating(data?.upvotes || 0, data?.downvotes || 0)}
|
||||
</Box>;
|
||||
}
|
||||
@ -30,6 +30,8 @@ func PostVotePolicies(c *gin.Context) (bool, []error) {
|
||||
case READ_VOTE:
|
||||
return rules.CheckRule(c, postvoterules.PostVoteReadRule)
|
||||
|
||||
default:
|
||||
return rules.CheckRule(c, postvoterules.PostVotesReadRule)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
package postvoterules
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func PostVotesReadRule(c *gin.Context) (bool, []error) {
|
||||
// rulesToCheck := []rules.RuleFunction{}
|
||||
|
||||
// isAllowed, errors := rules.CheckRules(
|
||||
// c,
|
||||
// rulesToCheck,
|
||||
// rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
// )
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@ -65,6 +65,25 @@ func (q *Queries) GetPostVote(ctx context.Context, arg GetPostVoteParams) (bool,
|
||||
return vote, err
|
||||
}
|
||||
|
||||
const getPostVotes = `-- name: GetPostVotes :one
|
||||
SELECT count (*) FILTER (WHERE vote = TRUE) as upvotes,
|
||||
count (*) FILTER (WHERE vote = FALSE) as downvotes
|
||||
FROM public.post_votes
|
||||
WHERE post_id = $1
|
||||
`
|
||||
|
||||
type GetPostVotesRow struct {
|
||||
Upvotes int64 `json:"upvotes"`
|
||||
Downvotes int64 `json:"downvotes"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetPostVotes(ctx context.Context, postID int64) (GetPostVotesRow, error) {
|
||||
row := q.db.QueryRow(ctx, getPostVotes, postID)
|
||||
var i GetPostVotesRow
|
||||
err := row.Scan(&i.Upvotes, &i.Downvotes)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateVote = `-- name: UpdateVote :one
|
||||
UPDATE public.post_votes
|
||||
SET vote=$1
|
||||
|
||||
@ -21,3 +21,9 @@ RETURNING *;
|
||||
SELECT vote
|
||||
FROM public.post_votes p_v
|
||||
WHERE p_v.user_id = $1 and p_v.post_id = $2;
|
||||
|
||||
-- name: GetPostVotes :one
|
||||
SELECT count (*) FILTER (WHERE vote = TRUE) as upvotes,
|
||||
count (*) FILTER (WHERE vote = FALSE) as downvotes
|
||||
FROM public.post_votes
|
||||
WHERE post_id = $1;
|
||||
|
||||
@ -3,13 +3,14 @@ package middleware
|
||||
import (
|
||||
postvotespolicies "enshi/ABAC/PostVotesPolicies"
|
||||
"enshi/ABAC/rules"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func PostVotesMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
a := strings.Split(c.Request.URL.Path, "/")[1]
|
||||
switch c.Request.Method {
|
||||
case "DELETE":
|
||||
c.Set("target", postvotespolicies.DELETE_VOTE)
|
||||
@ -18,7 +19,11 @@ func PostVotesMiddleware() gin.HandlerFunc {
|
||||
c.Set("target", postvotespolicies.CREATE_VOTE)
|
||||
|
||||
case "GET":
|
||||
c.Set("target", postvotespolicies.READ_VOTE)
|
||||
if a != "post-votes" {
|
||||
c.Set("target", postvotespolicies.READ_VOTE)
|
||||
} else {
|
||||
c.Set("target", "")
|
||||
}
|
||||
}
|
||||
|
||||
isAllowed, errors := postvotespolicies.PostVotePolicies(c)
|
||||
|
||||
@ -155,10 +155,15 @@ func SetupRotes(g *gin.Engine) error {
|
||||
)
|
||||
|
||||
postVoteGroup.GET(
|
||||
"post-votes/:post-id",
|
||||
"post-vote/:post-id",
|
||||
voteroutes.GetVote,
|
||||
)
|
||||
|
||||
postVoteGroup.GET(
|
||||
"post-votes/:post-id",
|
||||
voteroutes.GetVotes,
|
||||
)
|
||||
|
||||
// Admin group routes
|
||||
adminGroup := g.Group("/admin/")
|
||||
adminGroup.Use(middleware.AdminMiddleware())
|
||||
|
||||
@ -25,6 +25,13 @@ func CreateVote(c *gin.Context) {
|
||||
}
|
||||
postVoteParams.UserID = userId
|
||||
|
||||
postId, err := getters.GetInt64Param(c, "post-id")
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
postVoteParams.PostID = postId
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
if _, err := query.CreatePostVote(context.Background(), postVoteParams); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
|
||||
@ -14,11 +14,6 @@ import (
|
||||
func GetVote(c *gin.Context) {
|
||||
var postVoteParams db_repo.GetPostVoteParams
|
||||
|
||||
if err := c.BindJSON(&postVoteParams); err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userId, err := getters.GetUserIdFromContext(c)
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
@ -26,6 +21,13 @@ func GetVote(c *gin.Context) {
|
||||
}
|
||||
postVoteParams.UserID = userId
|
||||
|
||||
postId, err := getters.GetInt64Param(c, "post-id")
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
postVoteParams.PostID = postId
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
if voteData, err := query.GetPostVote(context.Background(), postVoteParams); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
|
||||
29
enshi_back/routes/voteRoutes/getVotes.go
Normal file
29
enshi_back/routes/voteRoutes/getVotes.go
Normal file
@ -0,0 +1,29 @@
|
||||
package voteroutes
|
||||
|
||||
import (
|
||||
"context"
|
||||
rest_api_stuff "enshi/REST_API_stuff"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"enshi/middleware/getters"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetVotes(c *gin.Context) {
|
||||
postId, err := getters.GetInt64Param(c, "post-id")
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
if voteData, err := query.GetPostVotes(context.Background(), postId); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
} else {
|
||||
c.IndentedJSON(http.StatusOK, voteData)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user