diff --git a/.vscode/settings.json b/.vscode/settings.json
index ce758c7..fd2040b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,7 @@
{
"cSpell.words": [
- "godotenv"
+ "downvotes",
+ "godotenv",
+ "upvotes"
]
}
\ No newline at end of file
diff --git a/enshi/src/Components/ArticleViewer/ArticleViewer.tsx b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx
index 15f8a4a..892920a 100644
--- a/enshi/src/Components/ArticleViewer/ArticleViewer.tsx
+++ b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx
@@ -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) {
{data.title}
-
-
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx
new file mode 100644
index 0000000..46a880b
--- /dev/null
+++ b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx
@@ -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 (
+ voteMutation.mutate()}
+ >
+ {props.vote ? : }
+
+ );
+}
diff --git a/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx b/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx
new file mode 100644
index 0000000..4d54842
--- /dev/null
+++ b/enshi/src/Components/ArticleViewer/VoteCounter/VoteCounter.tsx
@@ -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
+ {calculateRating(0, 0)}
+
+ }
+
+ return
+ {calculateRating(data?.upvotes || 0, data?.downvotes || 0)}
+ ;
+}
diff --git a/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go b/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go
index 045a327..9e597bf 100644
--- a/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go
@@ -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
diff --git a/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readVotesRule.go b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readVotesRule.go
new file mode 100644
index 0000000..736684e
--- /dev/null
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readVotesRule.go
@@ -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
+}
diff --git a/enshi_back/db/go_queries/post_votes_queries.sql.go b/enshi_back/db/go_queries/post_votes_queries.sql.go
index 688d23e..05e957c 100644
--- a/enshi_back/db/go_queries/post_votes_queries.sql.go
+++ b/enshi_back/db/go_queries/post_votes_queries.sql.go
@@ -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
diff --git a/enshi_back/db/queries/post_votes_queries.sql b/enshi_back/db/queries/post_votes_queries.sql
index ee6b651..ea1443a 100644
--- a/enshi_back/db/queries/post_votes_queries.sql
+++ b/enshi_back/db/queries/post_votes_queries.sql
@@ -20,4 +20,10 @@ RETURNING *;
-- name: GetPostVote :one
SELECT vote
FROM public.post_votes p_v
-WHERE p_v.user_id = $1 and p_v.post_id = $2;
\ No newline at end of file
+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;
diff --git a/enshi_back/middleware/postVotesMiddleware.go b/enshi_back/middleware/postVotesMiddleware.go
index f6805b9..0992fab 100644
--- a/enshi_back/middleware/postVotesMiddleware.go
+++ b/enshi_back/middleware/postVotesMiddleware.go
@@ -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)
diff --git a/enshi_back/routes/routesSetup.go b/enshi_back/routes/routesSetup.go
index 99e4133..2a21d4f 100644
--- a/enshi_back/routes/routesSetup.go
+++ b/enshi_back/routes/routesSetup.go
@@ -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())
diff --git a/enshi_back/routes/voteRoutes/createVote.go b/enshi_back/routes/voteRoutes/createVote.go
index 58946dc..037317c 100644
--- a/enshi_back/routes/voteRoutes/createVote.go
+++ b/enshi_back/routes/voteRoutes/createVote.go
@@ -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)
diff --git a/enshi_back/routes/voteRoutes/getVote.go b/enshi_back/routes/voteRoutes/getVote.go
index 609e74d..a949618 100644
--- a/enshi_back/routes/voteRoutes/getVote.go
+++ b/enshi_back/routes/voteRoutes/getVote.go
@@ -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)
diff --git a/enshi_back/routes/voteRoutes/getVotes.go b/enshi_back/routes/voteRoutes/getVotes.go
new file mode 100644
index 0000000..a259e64
--- /dev/null
+++ b/enshi_back/routes/voteRoutes/getVotes.go
@@ -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)
+ }
+}