Bookmarks and layout
This commit is contained in:
parent
66178664f3
commit
9aa5cbc12a
@ -1,19 +1,40 @@
|
||||
import { Container } from "@radix-ui/themes";
|
||||
import { Container, Separator, Text } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Interweave } from "interweave";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import SkeletonLoader from "./SkeletonLoader/SkeletonLoader";
|
||||
|
||||
type TArticleViewer = {
|
||||
htmlToParse?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export default function ArticleViewer(props: TArticleViewer) {
|
||||
const queryPapms = useParams()
|
||||
let queryParams = useParams();
|
||||
|
||||
const { data, isPending } = useQuery({
|
||||
queryKey: [`post_${queryParams["postId"]}`],
|
||||
queryFn: async () => {
|
||||
const response = await axiosLocalhost.get(
|
||||
`posts/${queryParams["postId"]}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
if (isPending)
|
||||
return (
|
||||
<SkeletonLoader />
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ql-snow">
|
||||
<Container className="mt-4 ql-editor">
|
||||
<Interweave content={props?.htmlToParse || ""} />
|
||||
<Container size={"2"} className="mt-4">
|
||||
<Text className="mb-2" as="div" size={"9"}>{data.title}</Text>
|
||||
<Separator size={"4"} className="mb-2" />
|
||||
<Interweave content={data.content} />
|
||||
</Container>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
import { Container, Skeleton, Text } from "@radix-ui/themes";
|
||||
import {
|
||||
headerLong,
|
||||
headerShort,
|
||||
pText,
|
||||
} from "../../../constants/textForSkeleton";
|
||||
|
||||
export default function SkeletonLoader() {
|
||||
return (
|
||||
<Container size={"2"} className="mt-4">
|
||||
<Skeleton>
|
||||
<Text size={"6"}>{headerLong}</Text>
|
||||
<br />
|
||||
<Text size={"6"}>{headerShort}</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Text>{pText}</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Text wrap={"pretty"}>{pText}</Text>
|
||||
<br />
|
||||
<br />
|
||||
<Text>{pText}</Text>
|
||||
</Skeleton>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Box, Container, Flex } from "@radix-ui/themes";
|
||||
import { useSetAtom } from "jotai";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import {
|
||||
postCreationAtom,
|
||||
postCreationTitleAtom
|
||||
@ -8,7 +8,7 @@ import Editor from "../../Components/Editor/Editor";
|
||||
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
||||
|
||||
export default function PostCreatorPage() {
|
||||
const setTitleValue = useSetAtom(postCreationTitleAtom);
|
||||
const [titleValue, setTitleValue] = useAtom(postCreationTitleAtom);
|
||||
const setContentValue = useSetAtom(postCreationAtom);
|
||||
|
||||
return (
|
||||
@ -25,6 +25,7 @@ export default function PostCreatorPage() {
|
||||
onChange={(e) => {
|
||||
setTitleValue(e.target.value);
|
||||
}}
|
||||
value={titleValue}
|
||||
/>
|
||||
</Container>
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Button } from "@radix-ui/themes";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { useAtom } from "jotai";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@ -16,15 +16,20 @@ type TSubmitPostButton = {
|
||||
|
||||
export default function SubmitPostButton(props: TSubmitPostButton) {
|
||||
const { t } = useTranslation();
|
||||
const contentValue = useAtomValue(postCreationAtom);
|
||||
const titleValue = useAtomValue(postCreationTitleAtom);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [isDisabled, setIsDisabled] = useState(false);
|
||||
|
||||
const [contentValue, setContentValue] = useAtom(postCreationAtom);
|
||||
const [titleValue, setTitleValue] = useAtom(postCreationTitleAtom);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const postMutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!titleValue) throw new Error("no title provided");
|
||||
if (!contentValue || contentValue === "<p><br></p>")
|
||||
throw new Error("no content provided");
|
||||
|
||||
axiosLocalhost.post("/posts", {
|
||||
title: titleValue,
|
||||
content: contentValue,
|
||||
@ -37,6 +42,8 @@ export default function SubmitPostButton(props: TSubmitPostButton) {
|
||||
setIsDisabled(false);
|
||||
},
|
||||
onSuccess: () => {
|
||||
setContentValue("");
|
||||
setTitleValue("");
|
||||
navigate("/");
|
||||
},
|
||||
});
|
||||
|
||||
12
enshi/src/constants/textForSkeleton.ts
Normal file
12
enshi/src/constants/textForSkeleton.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export const pText = `The goal of typography is to relate font size, line
|
||||
height, and line width in a proportional way that
|
||||
maximizes beauty and makes reading easier and more
|
||||
pleasant. The question is: What proportion(s) will give
|
||||
us the best results? The golden ratio is often observed
|
||||
in nature where beauty and utility intersect; perhaps we
|
||||
can use this “divine” proportion to enhance these
|
||||
attributes in our typography.`;
|
||||
|
||||
export const headerLong = `THUS SHU SHU HDFQIUWKHFQWHF KJQWHqwfiqfquwdhqwjdk`;
|
||||
|
||||
export const headerShort = `THUS SHU SHU HDFQIUWKHFQWHF`;
|
||||
@ -4,6 +4,7 @@ import {
|
||||
Route,
|
||||
useRouteError,
|
||||
} from "react-router-dom";
|
||||
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
||||
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
||||
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
||||
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
||||
@ -36,7 +37,9 @@ export const routes = createRoutesFromElements(
|
||||
<PostCreatorPage />
|
||||
</AuthPageWrapper>
|
||||
}
|
||||
></Route>
|
||||
/>
|
||||
|
||||
<Route path="/posts/:postId" element={<ArticleViewer />} />
|
||||
</Route>
|
||||
|
||||
<Route
|
||||
|
||||
36
enshi_back/ABAC/BookmarkPolicies/bookmarkPolicies.go
Normal file
36
enshi_back/ABAC/BookmarkPolicies/bookmarkPolicies.go
Normal file
@ -0,0 +1,36 @@
|
||||
package bookmarkspolicies
|
||||
|
||||
import (
|
||||
bookmarksrules "enshi/ABAC/BookmarkPolicies/bookmarkRules"
|
||||
"enshi/ABAC/rules"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
DELETE_BOOKMARK = "delete_bookmark"
|
||||
CREATE_BOOKMARK = "create_bookmark"
|
||||
READ_BOOKMARK = "read_bookmark"
|
||||
)
|
||||
|
||||
func BlogPolicies(c *gin.Context) (bool, []error) {
|
||||
target, exists := c.Get("target")
|
||||
if !exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Permit if one permit
|
||||
switch target {
|
||||
case DELETE_BOOKMARK:
|
||||
return rules.CheckRule(c, bookmarksrules.BookmarkDeleteRule)
|
||||
|
||||
case CREATE_BOOKMARK:
|
||||
return rules.CheckRule(c, bookmarksrules.BookmarkCreateRule)
|
||||
|
||||
case READ_BOOKMARK:
|
||||
return rules.CheckRule(c, bookmarksrules.BookmarkReadRule)
|
||||
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/createRule.go
Normal file
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/createRule.go
Normal file
@ -0,0 +1,22 @@
|
||||
package bookmarksrules
|
||||
|
||||
import (
|
||||
globalrules "enshi/ABAC/GlobalRules"
|
||||
"enshi/ABAC/rules"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func BookmarkCreateRule(c *gin.Context) (bool, []error) {
|
||||
rulesToCheck := []rules.RuleFunction{
|
||||
globalrules.AuthorizedRule,
|
||||
}
|
||||
|
||||
isAllowed, errors := rules.CheckRules(
|
||||
c,
|
||||
rulesToCheck,
|
||||
rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
)
|
||||
|
||||
return isAllowed, errors
|
||||
}
|
||||
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/deleteRule.go
Normal file
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/deleteRule.go
Normal file
@ -0,0 +1,22 @@
|
||||
package bookmarksrules
|
||||
|
||||
import (
|
||||
globalrules "enshi/ABAC/GlobalRules"
|
||||
"enshi/ABAC/rules"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func BookmarkDeleteRule(c *gin.Context) (bool, []error) {
|
||||
rulesToCheck := []rules.RuleFunction{
|
||||
globalrules.AuthorizedRule,
|
||||
}
|
||||
|
||||
isAllowed, errors := rules.CheckRules(
|
||||
c,
|
||||
rulesToCheck,
|
||||
rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
)
|
||||
|
||||
return isAllowed, errors
|
||||
}
|
||||
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/readRule.go
Normal file
22
enshi_back/ABAC/BookmarkPolicies/bookmarkRules/readRule.go
Normal file
@ -0,0 +1,22 @@
|
||||
package bookmarksrules
|
||||
|
||||
import (
|
||||
globalrules "enshi/ABAC/GlobalRules"
|
||||
"enshi/ABAC/rules"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func BookmarkReadRule(c *gin.Context) (bool, []error) {
|
||||
rulesToCheck := []rules.RuleFunction{
|
||||
globalrules.AuthorizedRule,
|
||||
}
|
||||
|
||||
isAllowed, errors := rules.CheckRules(
|
||||
c,
|
||||
rulesToCheck,
|
||||
rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
)
|
||||
|
||||
return isAllowed, errors
|
||||
}
|
||||
33
enshi_back/middleware/bookmarksMiddleware.go
Normal file
33
enshi_back/middleware/bookmarksMiddleware.go
Normal file
@ -0,0 +1,33 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
bookmarkspolicies "enshi/ABAC/BookmarkPolicies"
|
||||
"enshi/ABAC/rules"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func BookmarksMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
switch c.Request.Method {
|
||||
case "DELETE":
|
||||
c.Set("target", bookmarkspolicies.DELETE_BOOKMARK)
|
||||
|
||||
case "POST":
|
||||
c.Set("target", bookmarkspolicies.CREATE_BOOKMARK)
|
||||
|
||||
case "GET":
|
||||
c.Set("target", bookmarkspolicies.READ_BOOKMARK)
|
||||
}
|
||||
|
||||
isAllowed, errors := bookmarkspolicies.BlogPolicies(c)
|
||||
|
||||
if rules.ShouldAbortRequest(c, isAllowed, errors) {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
35
enshi_back/routes/bookmarksRoutes/createBookmark.go
Normal file
35
enshi_back/routes/bookmarksRoutes/createBookmark.go
Normal file
@ -0,0 +1,35 @@
|
||||
package bookmarksroutes
|
||||
|
||||
import (
|
||||
"context"
|
||||
rest_api_stuff "enshi/REST_API_stuff"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"enshi/middleware/getters"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CreatePost(c *gin.Context) {
|
||||
var bookmarkParams db_repo.CreateBookmarkParams
|
||||
|
||||
if err := c.BindJSON(&bookmarkParams); err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userId, err := getters.GetUserIdFromContext(c)
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
bookmarkParams.UserID = userId
|
||||
|
||||
query := db_repo.New(db_connection.Dbx)
|
||||
if _, err := query.CreateBookmark(context.Background(), bookmarkParams); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
rest_api_stuff.OkAnswer(c, "Bookmark has been created!")
|
||||
}
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"enshi/middleware/getters"
|
||||
"enshi/routes/authRoutes"
|
||||
"enshi/routes/blogRoutes"
|
||||
bookmarksroutes "enshi/routes/bookmarksRoutes"
|
||||
"enshi/routes/postsRoutes"
|
||||
"enshi/routes/userProfileRoutes"
|
||||
"net/http"
|
||||
@ -114,6 +115,14 @@ func SetupRotes(g *gin.Engine) error {
|
||||
userProfileRoutes.UpdateUserProfile,
|
||||
)
|
||||
|
||||
bookmarksGroup := g.Group("/")
|
||||
bookmarksGroup.Use(middleware.BookmarksMiddleware())
|
||||
|
||||
bookmarksGroup.POST(
|
||||
"bookmarks/:post-id",
|
||||
bookmarksroutes.CreateBookmark,
|
||||
)
|
||||
|
||||
// Admin group routes
|
||||
adminGroup := g.Group("/admin/")
|
||||
adminGroup.Use(middleware.AdminMiddleware())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user