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 { Interweave } from "interweave";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import { axiosLocalhost } from "../../api/axios/axios";
|
||||||
|
import SkeletonLoader from "./SkeletonLoader/SkeletonLoader";
|
||||||
|
|
||||||
type TArticleViewer = {
|
type TArticleViewer = {
|
||||||
htmlToParse?: string;
|
htmlToParse?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function ArticleViewer(props: TArticleViewer) {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="ql-snow">
|
<div className="ql-snow">
|
||||||
<Container className="mt-4 ql-editor">
|
<Container size={"2"} className="mt-4">
|
||||||
<Interweave content={props?.htmlToParse || ""} />
|
<Text className="mb-2" as="div" size={"9"}>{data.title}</Text>
|
||||||
|
<Separator size={"4"} className="mb-2" />
|
||||||
|
<Interweave content={data.content} />
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</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 { Box, Container, Flex } from "@radix-ui/themes";
|
||||||
import { useSetAtom } from "jotai";
|
import { useAtom, useSetAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
postCreationAtom,
|
postCreationAtom,
|
||||||
postCreationTitleAtom
|
postCreationTitleAtom
|
||||||
@ -8,7 +8,7 @@ import Editor from "../../Components/Editor/Editor";
|
|||||||
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
||||||
|
|
||||||
export default function PostCreatorPage() {
|
export default function PostCreatorPage() {
|
||||||
const setTitleValue = useSetAtom(postCreationTitleAtom);
|
const [titleValue, setTitleValue] = useAtom(postCreationTitleAtom);
|
||||||
const setContentValue = useSetAtom(postCreationAtom);
|
const setContentValue = useSetAtom(postCreationAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -25,6 +25,7 @@ export default function PostCreatorPage() {
|
|||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTitleValue(e.target.value);
|
setTitleValue(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
value={titleValue}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button } from "@radix-ui/themes";
|
import { Button } from "@radix-ui/themes";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@ -16,15 +16,20 @@ type TSubmitPostButton = {
|
|||||||
|
|
||||||
export default function SubmitPostButton(props: TSubmitPostButton) {
|
export default function SubmitPostButton(props: TSubmitPostButton) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const contentValue = useAtomValue(postCreationAtom);
|
|
||||||
const titleValue = useAtomValue(postCreationTitleAtom);
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [isDisabled, setIsDisabled] = useState(false);
|
const [isDisabled, setIsDisabled] = useState(false);
|
||||||
|
|
||||||
|
const [contentValue, setContentValue] = useAtom(postCreationAtom);
|
||||||
|
const [titleValue, setTitleValue] = useAtom(postCreationTitleAtom);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const postMutation = useMutation({
|
const postMutation = useMutation({
|
||||||
mutationFn: async () => {
|
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", {
|
axiosLocalhost.post("/posts", {
|
||||||
title: titleValue,
|
title: titleValue,
|
||||||
content: contentValue,
|
content: contentValue,
|
||||||
@ -37,6 +42,8 @@ export default function SubmitPostButton(props: TSubmitPostButton) {
|
|||||||
setIsDisabled(false);
|
setIsDisabled(false);
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
setContentValue("");
|
||||||
|
setTitleValue("");
|
||||||
navigate("/");
|
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,
|
Route,
|
||||||
useRouteError,
|
useRouteError,
|
||||||
} from "react-router-dom";
|
} from "react-router-dom";
|
||||||
|
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
||||||
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
||||||
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
||||||
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
||||||
@ -36,7 +37,9 @@ export const routes = createRoutesFromElements(
|
|||||||
<PostCreatorPage />
|
<PostCreatorPage />
|
||||||
</AuthPageWrapper>
|
</AuthPageWrapper>
|
||||||
}
|
}
|
||||||
></Route>
|
/>
|
||||||
|
|
||||||
|
<Route path="/posts/:postId" element={<ArticleViewer />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<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/middleware/getters"
|
||||||
"enshi/routes/authRoutes"
|
"enshi/routes/authRoutes"
|
||||||
"enshi/routes/blogRoutes"
|
"enshi/routes/blogRoutes"
|
||||||
|
bookmarksroutes "enshi/routes/bookmarksRoutes"
|
||||||
"enshi/routes/postsRoutes"
|
"enshi/routes/postsRoutes"
|
||||||
"enshi/routes/userProfileRoutes"
|
"enshi/routes/userProfileRoutes"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -114,6 +115,14 @@ func SetupRotes(g *gin.Engine) error {
|
|||||||
userProfileRoutes.UpdateUserProfile,
|
userProfileRoutes.UpdateUserProfile,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bookmarksGroup := g.Group("/")
|
||||||
|
bookmarksGroup.Use(middleware.BookmarksMiddleware())
|
||||||
|
|
||||||
|
bookmarksGroup.POST(
|
||||||
|
"bookmarks/:post-id",
|
||||||
|
bookmarksroutes.CreateBookmark,
|
||||||
|
)
|
||||||
|
|
||||||
// Admin group routes
|
// Admin group routes
|
||||||
adminGroup := g.Group("/admin/")
|
adminGroup := g.Group("/admin/")
|
||||||
adminGroup.Use(middleware.AdminMiddleware())
|
adminGroup.Use(middleware.AdminMiddleware())
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user