@@ -65,11 +81,7 @@ const Editor = forwardRef((props: TEditor) => {
value={value}
ref={editor}
modules={modules}
-
-
onChange={changeHandler}
-
-
theme="snow"
placeholder="Type your thoughts here..."
/>
diff --git a/enshi/src/Components/NavBar/NavBar.tsx b/enshi/src/Components/NavBar/NavBar.tsx
index a3d630c..17472c6 100644
--- a/enshi/src/Components/NavBar/NavBar.tsx
+++ b/enshi/src/Components/NavBar/NavBar.tsx
@@ -1,67 +1,15 @@
-import { Button, Card, ChevronDownIcon, Text } from "@radix-ui/themes";
-import * as NavigationMenu from "@radix-ui/react-navigation-menu";
-import { useLocation, useNavigate } from "react-router-dom";
+import CustomNavigationMenu from "./NavigationMenu/NavigationMenu";
+import RightButtonBar from "./RightButtonBar/RightButtonBar";
+import SearchField from "./SearchField/SearchField";
export default function NavBar() {
return (
-
);
}
-
-type TNavItem = {
- text: string;
- to: string;
-};
-
-function NavItem(props: TNavItem) {
- const navigate = useNavigate();
- const location = useLocation();
-
- return (
-
-
-
-
-
- );
-}
diff --git a/enshi/src/Components/NavBar/NavigationMenu/NavigationMenu.tsx b/enshi/src/Components/NavBar/NavigationMenu/NavigationMenu.tsx
new file mode 100644
index 0000000..56e8b9a
--- /dev/null
+++ b/enshi/src/Components/NavBar/NavigationMenu/NavigationMenu.tsx
@@ -0,0 +1,62 @@
+import * as NavigationMenu from "@radix-ui/react-navigation-menu";
+import { Button, Heading, useThemeContext } from "@radix-ui/themes";
+import { useTranslation } from "react-i18next";
+import { useLocation, useNavigate } from "react-router-dom";
+
+export default function CustomNavigationMenu() {
+
+ const {t} = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+type TNavItem = {
+ text: string;
+ to: string;
+};
+
+function NavItem(props: TNavItem) {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const theme = useThemeContext();
+
+ return (
+
+
+
+
+
+
+
+
+ {location.pathname == props.to ? (
+
+ ) : null}
+
+ );
+}
diff --git a/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx b/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx
new file mode 100644
index 0000000..eaaaaa6
--- /dev/null
+++ b/enshi/src/Components/NavBar/RightButtonBar/CreatePostButton/CreatePostButton.tsx
@@ -0,0 +1,17 @@
+import { PlusIcon } from "@radix-ui/react-icons";
+import { Button, Text } from "@radix-ui/themes";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
+
+export default function CreatePostButton() {
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ );
+}
diff --git a/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx b/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx
new file mode 100644
index 0000000..3c1ff38
--- /dev/null
+++ b/enshi/src/Components/NavBar/RightButtonBar/RightButtonBar.tsx
@@ -0,0 +1,12 @@
+import CreatePostButton from "./CreatePostButton/CreatePostButton";
+import UserButton from "./UserButton/UserButton";
+
+
+export default function RightButtonBar() {
+ return (
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx b/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx
new file mode 100644
index 0000000..ce9ca61
--- /dev/null
+++ b/enshi/src/Components/NavBar/RightButtonBar/UserButton/UserButton.tsx
@@ -0,0 +1,76 @@
+import {
+ EnterIcon,
+ ExitIcon,
+ LaptopIcon,
+ PersonIcon,
+} from "@radix-ui/react-icons";
+import { DropdownMenu, Flex, IconButton, Text } from "@radix-ui/themes";
+import { Icon } from "@radix-ui/themes/dist/esm/components/callout.js";
+import { useAtomValue } from "jotai";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
+import { userAtom } from "../../../../AtomStore/AtomStore";
+
+export default function UserButton() {
+ const user = useAtomValue(userAtom);
+ const { t } = useTranslation();
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {t("profile")}
+
+
+
+
+
+
+
+
+
+
+ {t("yourBlogs")}
+
+
+
+
+
+
+
+ {user ? (
+
+
+
+
+ {t("signOut")}
+
+ ) : (
+
+
+
+
+
+ {t("signIn")}
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/enshi/src/Components/NavBar/SearchField/SearchField.tsx b/enshi/src/Components/NavBar/SearchField/SearchField.tsx
new file mode 100644
index 0000000..02dbf95
--- /dev/null
+++ b/enshi/src/Components/NavBar/SearchField/SearchField.tsx
@@ -0,0 +1,20 @@
+import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
+import { TextField } from "@radix-ui/themes";
+import { useTranslation } from "react-i18next";
+
+export default function SearchField() {
+ const {t} = useTranslation()
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/enshi/src/Components/ToastProvider/ToastProvider.tsx b/enshi/src/Components/ToastProvider/ToastProvider.tsx
new file mode 100644
index 0000000..51ec9a7
--- /dev/null
+++ b/enshi/src/Components/ToastProvider/ToastProvider.tsx
@@ -0,0 +1,44 @@
+import { Cross1Icon } from "@radix-ui/react-icons";
+import * as Toast from "@radix-ui/react-toast";
+import { Card, Text } from "@radix-ui/themes";
+import { useAtomValue } from "jotai";
+import React from "react";
+import { toastAtom } from "../../AtomStore/AtomStore";
+
+export default function ToastProvider(props: React.PropsWithChildren) {
+ const toastsToRender = useAtomValue(toastAtom);
+
+ return (
+
+ {props.children}
+
+ {toastsToRender.map((toast) => {
+ return (
+
+
+
+
+ {toast.title}
+
+
+
+ {toast.description}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/enshi/src/Components/UserNicknameLink/UserNicknameLink.tsx b/enshi/src/Components/UserNicknameLink/UserNicknameLink.tsx
new file mode 100644
index 0000000..05091f5
--- /dev/null
+++ b/enshi/src/Components/UserNicknameLink/UserNicknameLink.tsx
@@ -0,0 +1,33 @@
+import { Skeleton, Text } from "@radix-ui/themes";
+import { useQuery } from "@tanstack/react-query";
+import { Link } from "react-router-dom";
+import { axiosLocalhost } from "../../api/axios/axios";
+
+type TUserNicknameLink = {
+ userId: string;
+};
+
+export default function UserNicknameLink(props: TUserNicknameLink) {
+ const { data, isPending } = useQuery({
+ queryKey: [`userLink${props.userId}`],
+ queryFn: async () => {
+ const response = await axiosLocalhost.get(
+ `/user/${props.userId || 0}`
+ );
+ return response.data as string;
+ },
+ });
+
+ if (isPending)
+ return (
+
+ @Nickname
+
+ );
+
+ return (
+
+
@{data}
+
+ );
+}
diff --git a/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx b/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx
new file mode 100644
index 0000000..3418d83
--- /dev/null
+++ b/enshi/src/Pages/AuthPageWrapper/AuthPageWrapper.tsx
@@ -0,0 +1,22 @@
+import { Container, Text } from "@radix-ui/themes";
+import { t } from "i18next";
+import { useAtomValue } from "jotai";
+import React from "react";
+import { useNavigate } from "react-router-dom";
+import { userAtom } from "../../AtomStore/AtomStore";
+
+export default function AuthPageWrapper(props: React.PropsWithChildren) {
+ const user = useAtomValue(userAtom);
+ const navigate = useNavigate();
+
+ if (!user) {
+ navigate("/login");
+ return (
+
+ {t("errors.unauthorized")}
+
+ );
+ }
+
+ return props.children;
+}
diff --git a/enshi/src/Pages/BlogPage/BlogPage.tsx b/enshi/src/Pages/BlogPage/BlogPage.tsx
new file mode 100644
index 0000000..fc1ae4a
--- /dev/null
+++ b/enshi/src/Pages/BlogPage/BlogPage.tsx
@@ -0,0 +1,13 @@
+import { Box } from '@radix-ui/themes'
+import { useParams } from 'react-router-dom'
+
+
+export default function BlogPage() {
+ const queryParams = useParams()
+
+ return (
+
+
+
+ )
+}
diff --git a/enshi/src/Pages/LoginRegisterPage/LoginPage/LoginPage.tsx b/enshi/src/Pages/LoginRegisterPage/LoginPage/LoginPage.tsx
new file mode 100644
index 0000000..2b1e3db
--- /dev/null
+++ b/enshi/src/Pages/LoginRegisterPage/LoginPage/LoginPage.tsx
@@ -0,0 +1,182 @@
+import * as Form from "@radix-ui/react-form";
+import { CrossCircledIcon } from "@radix-ui/react-icons";
+import { Button, Card, Heading, Text, TextField } from "@radix-ui/themes";
+import { useMutation } from "@tanstack/react-query";
+import { t } from "i18next";
+import { useAtom } from "jotai";
+import { useState } from "react";
+import { Link, useNavigate } from "react-router-dom";
+import { axiosLocalhost } from "../../../api/axios/axios";
+import { userAtom } from "../../../AtomStore/AtomStore";
+import UseCapsLock from "../../../hooks/useCapsLock";
+import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
+
+type TLoginData = {
+ username: string;
+ password: string;
+};
+
+export default function LoginPage() {
+ const [userAtomValue, setUserAtom] = useAtom(userAtom);
+ const [showPassword, setShowPassword] = useState(false);
+ const { isCapsLockOn } = UseCapsLock();
+ const [isError, setIsError] = useState(false);
+
+ const navigate = useNavigate();
+
+ const logInMutation = useMutation({
+ mutationFn: async (data: TLoginData) => {
+ let response = await axiosLocalhost.post(
+ "/login",
+ JSON.stringify(data)
+ );
+ setUserAtom({
+ username: response.data.username,
+ isAdmin: false,
+ id: response.data.id,
+ });
+ },
+
+ onError: (error, _variables, _context) => {
+ console.log(error);
+ setIsError(true);
+ },
+
+ onSuccess: () => {
+ let isAdminFunc = async () => {
+ let response = await axiosLocalhost.get("/admin/check");
+ if (response.status === 200) {
+ setUserAtom({
+ username: userAtomValue?.username || "",
+ isAdmin: true,
+ id: userAtomValue?.id,
+ });
+ }
+ };
+
+ isAdminFunc();
+
+ navigate("/");
+ },
+ });
+
+ return (
+
+
+ {t("loginForm")}
+
+ {
+ e.preventDefault();
+ let formData = new FormData(
+ document.querySelector("form") as HTMLFormElement
+ );
+
+ let loginData: TLoginData = {
+ password: (formData.get("password") as string) || "",
+ username: (formData.get("username") as string) || "",
+ };
+
+ logInMutation.mutate(loginData);
+ }}
+ >
+
+
+
+ {t("username")}
+
+
+ {t("errors.enterUsername")}
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {t("password")}
+
+
+ {t("errors.enterPassword")}
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+ {t("capsLogWarning")}
+
+
+
+
+ {t("errors.invalidLoginData")}
+
+
+
+
+
+
+
+ {t("suggestRegister")}{" "}
+
+ {t("register")}
+ {" "}
+ {t("now")}
+
+
+
+ );
+}
diff --git a/enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx b/enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx
new file mode 100644
index 0000000..50d1e9b
--- /dev/null
+++ b/enshi/src/Pages/LoginRegisterPage/PostRedactor/PostRedactor.tsx
@@ -0,0 +1,76 @@
+import { Box, Container, Flex, Spinner } from "@radix-ui/themes";
+import { useQuery } from "@tanstack/react-query";
+import { useState } from "react";
+import { useParams } from "react-router-dom";
+import { axiosLocalhost } from "../../../api/axios/axios";
+import Editor from "../../../Components/Editor/Editor";
+import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton";
+
+export default function PostRedactor() {
+ const [contentValue, setContentValue] = useState("");
+ const [titleValue, setTitleValue] = useState("");
+
+ const queryParams = useParams();
+
+ const { isPending } = useQuery({
+ queryKey: ["changePostKey", queryParams.postId],
+ queryFn: async () => {
+ try {
+ const response = await axiosLocalhost.get(
+ `/posts/${queryParams.postId}`
+ );
+
+ setTitleValue(response.data["title"]);
+ setContentValue(response.data["content"]);
+
+ return response.data;
+ } catch (error) {
+ console.log(error);
+
+ return error;
+ }
+ },
+ gcTime: 0,
+ refetchOnMount: true
+ });
+
+ return (
+ <>
+
+
+
+ {
+ setTitleValue(e.target.value);
+ }}
+ value={titleValue}
+ />
+
+
+
+ {isPending ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx b/enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx
new file mode 100644
index 0000000..070448d
--- /dev/null
+++ b/enshi/src/Pages/LoginRegisterPage/PostRedactor/SubmitChangesButton/SubmitChangesButton.tsx
@@ -0,0 +1,60 @@
+import { Button } from "@radix-ui/themes";
+import { useMutation } from "@tanstack/react-query";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate, useParams } from "react-router-dom";
+import { axiosLocalhost } from "../../../../api/axios/axios";
+import useToast from "../../../../hooks/useToast";
+
+type TSubmitChangesButton = {
+ className: string;
+ titleValue: string;
+ contentValue: string;
+};
+
+export default function SubmitChangesButton(props: TSubmitChangesButton) {
+ const { t } = useTranslation();
+
+ const [isDisabled, setIsDisabled] = useState(false);
+ const createToast = useToast();
+
+ const navigate = useNavigate();
+ const queryParams = useParams();
+
+ const postMutation = useMutation({
+ mutationFn: async () => {
+ if (!props.titleValue) throw new Error("no title provided");
+ if (!props.contentValue || props.contentValue === "
")
+ throw new Error("no content provided");
+
+ axiosLocalhost.put(`/posts/${queryParams["postId"]}`, {
+ title: props.titleValue,
+ content: props.contentValue,
+ });
+ },
+ onMutate: () => {
+ setIsDisabled(true);
+ },
+ onError: () => {
+ setIsDisabled(false);
+ },
+ onSuccess: () => {
+ createToast({title: "Post has been changed!"})
+ navigate("/");
+ },
+ });
+
+ return (
+
+ );
+}
diff --git a/enshi/src/Pages/LoginRegisterPage/RegisterPage/RegisterPage.tsx b/enshi/src/Pages/LoginRegisterPage/RegisterPage/RegisterPage.tsx
new file mode 100644
index 0000000..4702221
--- /dev/null
+++ b/enshi/src/Pages/LoginRegisterPage/RegisterPage/RegisterPage.tsx
@@ -0,0 +1,265 @@
+import * as Form from "@radix-ui/react-form";
+import { CrossCircledIcon } from "@radix-ui/react-icons";
+import { Button, Card, Heading, Text, TextField } from "@radix-ui/themes";
+import { useMutation } from "@tanstack/react-query";
+import { useSetAtom } from "jotai";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Link, useNavigate } from "react-router-dom";
+import { axiosLocalhost } from "../../../api/axios/axios";
+import { userAtom } from "../../../AtomStore/AtomStore";
+import UseCapsLock from "../../../hooks/useCapsLock";
+import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
+
+type TRegisterData = {
+ username: string;
+ password: string;
+ email: string;
+};
+
+export default function RegisterPage() {
+ const setUserAtom = useSetAtom(userAtom)
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfPassword, setShowConfPassword] = useState(false);
+ const { isCapsLockOn } = UseCapsLock();
+
+ const { t } = useTranslation();
+
+ const [isError, setIsError] = useState(false);
+
+ const navigate = useNavigate();
+
+ const registerMutation = useMutation({
+ mutationFn: async (data: TRegisterData) => {
+ let response = await axiosLocalhost.post("/users", JSON.stringify(data));
+ setUserAtom({
+ username: response.data.username,
+ isAdmin: false,
+ id: response.data.id,
+ })
+ },
+
+ onError: (error, _variables, _context) => {
+ console.log(error);
+ setIsError(true);
+ },
+
+ onSuccess: () => {
+ navigate("/");
+ },
+ });
+
+ return (
+
+
+ {t("registerForm")}
+
+ {
+ e.preventDefault();
+ let formData = new FormData(
+ document.querySelector("form") as HTMLFormElement
+ );
+
+ let registerData: TRegisterData = {
+ password: (formData.get("password") as string) || "",
+ username: (formData.get("username") as string) || "",
+ email: (formData.get("email") as string) || "",
+ };
+
+ registerMutation.mutate(registerData);
+ }}
+ >
+
+
+
+ {t("username")}
+
+
+ {t("errors.enterUsername")}
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {t("email")}
+
+
+ {t("errors.enterEmail")}
+
+
+ {t("errors.invalidEmail")}
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ {t("password")}
+
+
+ {t("errors.enterPassword")}
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+ {t("capsLogWarning")}
+
+
+
+
+
+
+ {t("confirmPassword")}
+
+
+ {t("errors.enterPassword")}
+
+
+ value !== formData.get("password")
+ }
+ >
+
+ {t("errors.passwordsMismatch")}
+
+
+
+
+
+
+ {(validity) => (
+
+
+
+ )}
+
+
+
+
+ {t("capsLogWarning")}
+
+
+
+
+ {t("errors.invalidRegisterData")}
+
+
+
+
+
+
+
+ {t("alreadyRegistered")}{" "}
+
+ {t("logIn")}
+ {" "}
+ {t("now")}
+
+
+
+ {t("byPressingTheButton")}{" "}
+
+ {t("termsOfService")}.
+
+
+
+
+ );
+}
diff --git a/enshi/src/Pages/LoginRegisterPage/ShowPasswordButton/ShowPasswordButton.tsx b/enshi/src/Pages/LoginRegisterPage/ShowPasswordButton/ShowPasswordButton.tsx
new file mode 100644
index 0000000..e7bd092
--- /dev/null
+++ b/enshi/src/Pages/LoginRegisterPage/ShowPasswordButton/ShowPasswordButton.tsx
@@ -0,0 +1,40 @@
+import { EyeClosedIcon, EyeOpenIcon } from "@radix-ui/react-icons";
+import { IconButton, Tooltip } from "@radix-ui/themes";
+import { Dispatch, SetStateAction } from "react";
+
+type TShowPasswordButton = {
+ isShown: boolean;
+ setIsShown: Dispatch
>;
+};
+
+export default function ShowPasswordButton({ isShown, setIsShown }: TShowPasswordButton) {
+ return (
+
+
+ {isShown ? (
+ {
+ setIsShown(!isShown);
+ }}
+ size={"1"}
+ className="rounded-full"
+ variant="soft"
+ >
+
+
+ ) : (
+ setIsShown(!isShown)}
+ size={"1"}
+ className="rounded-full"
+ variant="soft"
+ >
+
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/enshi/src/Pages/MainPage/MainPage.tsx b/enshi/src/Pages/MainPage/MainPage.tsx
deleted file mode 100644
index 12102a2..0000000
--- a/enshi/src/Pages/MainPage/MainPage.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react'
-import { Outlet } from 'react-router-dom'
-import NavBar from '../../Components/NavBar/NavBar'
-import { axiosLocalhost } from '../../api/axios/axios'
-
-export default function MainPage() {
- return (
- <>
-
-
-
- >
- )
-}
diff --git a/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx
new file mode 100644
index 0000000..eb677ba
--- /dev/null
+++ b/enshi/src/Pages/PostCreatorPage/PostCreatorPage.tsx
@@ -0,0 +1,43 @@
+import { Box, Container, Flex } from "@radix-ui/themes";
+import { useAtom, useSetAtom } from "jotai";
+import {
+ postCreationAtom,
+ postCreationTitleAtom
+} from "../../AtomStore/AtomStore";
+import Editor from "../../Components/Editor/Editor";
+import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
+
+export default function PostCreatorPage() {
+ const [titleValue, setTitleValue] = useAtom(postCreationTitleAtom);
+ const setContentValue = useSetAtom(postCreationAtom);
+
+ return (
+ <>
+
+
+
+
+ {
+ setTitleValue(e.target.value);
+ }}
+ value={titleValue}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx b/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx
new file mode 100644
index 0000000..201d22d
--- /dev/null
+++ b/enshi/src/Pages/PostCreatorPage/SubmitPostButton/SubmitPostButton.tsx
@@ -0,0 +1,64 @@
+import { Button } from "@radix-ui/themes";
+import { useMutation } from "@tanstack/react-query";
+import { useAtom } from "jotai";
+import { useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useNavigate } from "react-router-dom";
+import { axiosLocalhost } from "../../../api/axios/axios";
+import {
+ postCreationAtom,
+ postCreationTitleAtom,
+} from "../../../AtomStore/AtomStore";
+
+type TSubmitPostButton = {
+ className: string;
+};
+
+export default function SubmitPostButton(props: TSubmitPostButton) {
+ const { t } = useTranslation();
+
+ 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 === "
")
+ throw new Error("no content provided");
+
+ axiosLocalhost.post("/posts", {
+ title: titleValue,
+ content: contentValue,
+ });
+ },
+ onMutate: () => {
+ setIsDisabled(true);
+ },
+ onError: () => {
+ setIsDisabled(false);
+ },
+ onSuccess: () => {
+ setContentValue("");
+ setTitleValue("");
+ navigate("/");
+ },
+ });
+
+ return (
+
+ );
+}
diff --git a/enshi/src/Pages/RandomPostsPage/PostCard/PostCard.tsx b/enshi/src/Pages/RandomPostsPage/PostCard/PostCard.tsx
new file mode 100644
index 0000000..2b29f64
--- /dev/null
+++ b/enshi/src/Pages/RandomPostsPage/PostCard/PostCard.tsx
@@ -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 (
+
+
+
+
+
+
+
+ {post.title}
+
+
+
+ );
+}
diff --git a/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx b/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx
new file mode 100644
index 0000000..605e04f
--- /dev/null
+++ b/enshi/src/Pages/RandomPostsPage/RandomPostsPage.tsx
@@ -0,0 +1,67 @@
+import * as ScrollArea from "@radix-ui/react-scroll-area";
+import { Container, Flex, Heading, Separator } from "@radix-ui/themes";
+import { useQuery } from "@tanstack/react-query";
+import { useTranslation } from "react-i18next";
+import { GetRandomPostsRow } from "../../@types/PostTypes";
+import { axiosLocalhost } from "../../api/axios/axios";
+import PostCard from "./PostCard/PostCard";
+
+const LIMIT = 10;
+
+export default function RandomPostsPage() {
+ const {t} = useTranslation()
+
+ 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 (
+ <>
+
+
+ {t("discover")}
+
+
+
+
+
+
+ {data?.map((post, i) => {
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+ {/*
+
+ */}
+ {/* */}
+
+
+ >
+ );
+}
diff --git a/enshi/src/Pages/UserBlogsPage/SkeletonBoxes/SkeletonBoxes.tsx b/enshi/src/Pages/UserBlogsPage/SkeletonBoxes/SkeletonBoxes.tsx
new file mode 100644
index 0000000..2419e98
--- /dev/null
+++ b/enshi/src/Pages/UserBlogsPage/SkeletonBoxes/SkeletonBoxes.tsx
@@ -0,0 +1,17 @@
+import { Box, Skeleton } from "@radix-ui/themes";
+
+export default function SkeletonBoxes() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/enshi/src/Pages/UserBlogsPage/UserBlogsPage.tsx b/enshi/src/Pages/UserBlogsPage/UserBlogsPage.tsx
new file mode 100644
index 0000000..599ae6d
--- /dev/null
+++ b/enshi/src/Pages/UserBlogsPage/UserBlogsPage.tsx
@@ -0,0 +1,126 @@
+import * as Dialog from "@radix-ui/react-dialog";
+import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons";
+import {
+ Box,
+ Button,
+ Container,
+ Flex,
+ Separator,
+ Text,
+} from "@radix-ui/themes";
+import { useQuery } from "@tanstack/react-query";
+import { axiosLocalhost } from "../../api/axios/axios";
+import BlogBox from "../../Components/BlogBox/BlogBox";
+import { JSONWithInt64 } from "../../utils/idnex";
+import SkeletonBoxes from "./SkeletonBoxes/SkeletonBoxes";
+
+export default function UserBlogsPage() {
+ const { data, isPending, isFetching } = useQuery({
+ queryKey: ["userBlogs"],
+ queryFn: async () => {
+ const response = await axiosLocalhost.get("/user/blogs", {
+ transformResponse: [(data) => data],
+ });
+
+ let temp = JSONWithInt64(response.data);
+
+ return temp as any[];
+ },
+ });
+
+ if (isPending)
+ return (
+
+
+
+ );
+
+ return (
+
+
+
+
+ Your blogs
+
+
+
+
+ {data
+ ? data?.map((blog: any, b) => {
+ return (
+ <>
+
+ >
+ );
+ })
+ : null}
+
+
+
+
+
+
+
+
+
+ Create blog
+
+
+ Create your new blog.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/enshi/src/api/axios/axios.ts b/enshi/src/api/axios/axios.ts
index 1a352da..f15ef56 100644
--- a/enshi/src/api/axios/axios.ts
+++ b/enshi/src/api/axios/axios.ts
@@ -1,8 +1,12 @@
import axios from "axios";
+const environment = import.meta.env.VITE_ENV || 'development';
+// const environment = "docker"
+const baseURL = environment === "docker" ? "https://localhost/api/v1/" : "http://127.0.0.1:9876/";
+
export const axiosLocalhost = axios.create(
{
- baseURL: `http://localhost:9876/`,
+ baseURL: baseURL,
withCredentials: true,
headers: {
diff --git a/enshi/src/constants/textForSkeleton.ts b/enshi/src/constants/textForSkeleton.ts
new file mode 100644
index 0000000..b166637
--- /dev/null
+++ b/enshi/src/constants/textForSkeleton.ts
@@ -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`;
diff --git a/enshi/src/hooks/useCapsLock.tsx b/enshi/src/hooks/useCapsLock.tsx
new file mode 100644
index 0000000..04abe26
--- /dev/null
+++ b/enshi/src/hooks/useCapsLock.tsx
@@ -0,0 +1,27 @@
+import { useEffect, useState } from "react";
+
+export default function UseCapsLock() {
+
+ const [isCapsLockOn, setIsCapsLockOn] = useState(false);
+
+ useEffect(() => {
+ const f = (e: KeyboardEvent) => {
+ if (e.getModifierState("CapsLock")) {
+ setIsCapsLockOn(true);
+ } else {
+ setIsCapsLockOn(false);
+ }
+ };
+
+ document.addEventListener("keydown", f);
+
+ return () => {
+ document.removeEventListener("keydown", f);
+ };
+ }, []);
+
+
+ return {
+ isCapsLockOn
+ }
+}
\ No newline at end of file
diff --git a/enshi/src/hooks/useToast.tsx b/enshi/src/hooks/useToast.tsx
new file mode 100644
index 0000000..b4fd553
--- /dev/null
+++ b/enshi/src/hooks/useToast.tsx
@@ -0,0 +1,7 @@
+import { useSetAtom } from "jotai";
+import { setToastAtom } from "../AtomStore/AtomStore";
+
+export default function useToast() {
+ const createToast = useSetAtom(setToastAtom);
+ return createToast;
+}
diff --git a/enshi/src/index.css b/enshi/src/index.css
index 4ded44e..574086b 100644
--- a/enshi/src/index.css
+++ b/enshi/src/index.css
@@ -1,10 +1,19 @@
+@import url('https://fonts.googleapis.com/css2?family=Edu+AU+VIC+WA+NT+Pre:wght@400..700&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap');
+
@tailwind base;
@tailwind components;
@tailwind utilities;
+@layer components {
+ .center-of-parent {
+ @apply absolute top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%];
+ }
+}
+
.radix-themes {
- --default-font-family:
- --heading-font-family:
+ --default-font-family: "Times New Roman"; ;
+
+ --heading-font-family: "Edu AU VIC WA NT Pre", cursive;
/* Your custom font for components */
--code-font-family:
/* Your custom font for components */
diff --git a/enshi/src/layout/MainPage/MainPage.tsx b/enshi/src/layout/MainPage/MainPage.tsx
new file mode 100644
index 0000000..fe64d01
--- /dev/null
+++ b/enshi/src/layout/MainPage/MainPage.tsx
@@ -0,0 +1,74 @@
+import { Box, Flex, Spinner } from "@radix-ui/themes";
+import { useQuery } from "@tanstack/react-query";
+import { useSetAtom } from "jotai";
+import { Outlet } from "react-router-dom";
+import { axiosLocalhost } from "../../api/axios/axios";
+import { userAtom } from "../../AtomStore/AtomStore";
+import NavBar from "../../Components/NavBar/NavBar";
+
+const REFETCH_INTERVAL_IN_MINUTES = 5;
+const RETRY_INTERVAL_IN_SECONDS = 1;
+
+const SECONDS_IN_MINUTE = 60;
+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() {
+ const setUserData = useSetAtom(userAtom);
+
+ const { isPending } = useQuery({
+ queryKey: ["authKey"],
+ queryFn: async () => {
+ try {
+ const response = await axiosLocalhost.get("/auth/check");
+
+ setUserData({
+ isAdmin: response.data["is_admin"],
+ username: response.data["username"],
+ id: response.data["id"],
+ });
+ return true;
+ } catch (error) {
+ setUserData(undefined);
+ return false;
+ }
+ },
+ refetchInterval:
+ REFETCH_INTERVAL_IN_MINUTES * SECONDS_IN_MINUTE * MILLS_IN_SECOND,
+ refetchOnWindowFocus: true,
+ refetchOnReconnect: true,
+ gcTime: 10,
+
+ retry: 3,
+ retryDelay: (attempt) =>
+ attempt * RETRY_INTERVAL_IN_SECONDS * MILLS_IN_SECOND,
+ });
+
+ return (
+ <>
+ {isPending ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+ )}
+ >
+ );
+}
diff --git a/enshi/src/locale/en.ts b/enshi/src/locale/en.ts
index 5a576c9..caacecb 100644
--- a/enshi/src/locale/en.ts
+++ b/enshi/src/locale/en.ts
@@ -1,5 +1,53 @@
const en = {
- hello: "hello!"
-}
+ hello: "hello!",
+ search: "Search...",
+ username: "Username",
+ email: "Email",
+ password: "Password",
+ confirmPassword: "Confirm password",
+ submit: "Submit",
-export default en;
\ No newline at end of file
+ createPost: "Write post",
+
+ profile: "Profile",
+ yourBlogs: "Your blogs",
+
+ signIn: "Log in",
+ signOut: "Sign out",
+
+ capsLogWarning: "CapsLock is on",
+
+ registerForm: "Register",
+ loginForm: "Log in",
+
+ alreadyRegistered: "Already registered?",
+
+ suggestRegister: "Don't have an account?",
+ register: "Register",
+ now: "now!",
+
+ logIn: "Log in",
+
+ updatePost: "Update",
+
+ byPressingTheButton: "By pressing the submit button you agree with our",
+ termsOfService: "Terms Of Service",
+
+ discover: "Discover something new...",
+
+ home: "Home",
+ following: "Following",
+
+ errors: {
+ enterUsername: "Please enter your username",
+ enterEmail: "Please enter your email",
+ invalidEmail: "Please enter correct email",
+ enterPassword: "Please enter your password",
+ passwordsMismatch: "Passwords must be the same",
+ invalidLoginData: "Invalid username or password",
+ invalidRegisterData: "Invalid register data",
+ unauthorized: "You need to be authorized to do that",
+ },
+};
+
+export default en;
diff --git a/enshi/src/locale/ru.ts b/enshi/src/locale/ru.ts
index b202ef3..b50c34f 100644
--- a/enshi/src/locale/ru.ts
+++ b/enshi/src/locale/ru.ts
@@ -1,5 +1,56 @@
-const ru = {
- hello: "Привет!"
-}
-export default ru;
\ No newline at end of file
+const ru = {
+ hello: "Привет!",
+ search: "Поиск...",
+ username: "Имя пользователя",
+ email: "Электронная почта",
+ password: "Пароль",
+ confirmPassword: "Подтвердите пароль",
+ submit: "Подтвердить",
+
+ createPost: "Написать пост",
+
+ profile: "Профиль",
+ yourBlogs: "Ваши блоги",
+
+ signIn: "Войти",
+ signOut: "Выйти",
+
+ capsLogWarning: "Включён CapsLock",
+
+ registerForm: "Регистрация",
+ loginForm: "Вход",
+
+ alreadyRegistered: "Уже есть аккаунт?",
+
+ suggestRegister: "Не зарегистрированы?",
+ register: "Создайте аккаунт",
+ now: "сейчас!",
+
+ logIn: "Войдите",
+
+ byPressingTheButton: "Нажимая `Подтвердить`, вы соглашаетесь с нашими",
+ termsOfService: "Условиями предоставления услуг.",
+
+ updatePost: "Изменить",
+
+ discover: "Найдите что-то новое",
+
+ home: "Главная",
+ following: "Отслеживаемые",
+
+
+ errors: {
+ enterUsername: "Это обязательное поле",
+ enterEmail: "Это обязательное поле",
+ invalidEmail: "Некорректный адрес электронной почты",
+ enterPassword: "Пожалуйста, введите пароль",
+ passwordsMismatch: "Пароли должны быть одинаковыми",
+ invalidLoginData: "Неверное имя пользователя или пароль",
+ invalidRegisterData:
+ "Пользователь с таким адресом электронной почты или именем пользователя уже существует",
+ unauthorized: "Вы должны быть авторизованы, чтобы сделать это",
+ },
+};
+
+export default ru;
diff --git a/enshi/src/routes/routes.tsx b/enshi/src/routes/routes.tsx
index 153236e..ef8fa3e 100644
--- a/enshi/src/routes/routes.tsx
+++ b/enshi/src/routes/routes.tsx
@@ -1,7 +1,20 @@
-import { createRoutesFromElements, Route, useRouteError } from "react-router-dom"
-import MainPage from "../Pages/MainPage/MainPage"
-import {Text} from "@radix-ui/themes";
-
+import { Text } from "@radix-ui/themes";
+import {
+ createRoutesFromElements,
+ Outlet,
+ Route,
+ useRouteError,
+} from "react-router-dom";
+import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
+import MainPage from "../layout/MainPage/MainPage";
+import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
+import BlogPage from "../Pages/BlogPage/BlogPage";
+import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
+import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
+import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
+import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
+import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
+import UserBlogsPage from "../Pages/UserBlogsPage/UserBlogsPage";
function ErrorBoundary() {
let error = useRouteError();
@@ -12,16 +25,54 @@ function ErrorBoundary() {
export const routes = createRoutesFromElements(
<>
- }
- element={}
- >
- Cringer path} />
+ } element={}>
+ } />
+
Cringer path, but this a}
- >
+ path="a?/c"
+ element={
+
+ This page is yet to be created
+
+ }
+ />
+
+
+
+
+ }
+ />
+
+ } />
+
+ }>
+
+
+
+ }
+ />
+
+
+ } />
+ } />
+
+ }
+ element={}
+ />
+
+ }
+ element={}
+ />
>
-)
\ No newline at end of file
+);
diff --git a/enshi/src/utils/idnex.ts b/enshi/src/utils/idnex.ts
new file mode 100644
index 0000000..380864c
--- /dev/null
+++ b/enshi/src/utils/idnex.ts
@@ -0,0 +1,19 @@
+const isBigNumber = (num: any) => !Number.isSafeInteger(+num);
+
+const enquoteBigNumber = (jsonString: any, bigNumChecker: any) =>
+ jsonString.replaceAll(
+ /([:\s\[,]*)(\d+)([\s,\]]*)/g,
+ (matchingSubstr: any, prefix: any, bigNum: any, suffix: any) =>
+ bigNumChecker(bigNum)
+ ? `${prefix}"${bigNum}"${suffix}`
+ : matchingSubstr
+ );
+
+const parseWithBigInt = (jsonString: any, bigNumChecker: any) =>
+ JSON.parse(enquoteBigNumber(jsonString, bigNumChecker), (_key, value) =>
+ !isNaN(value) && bigNumChecker(value) ? BigInt(value).toString() : value
+ );
+
+export const JSONWithInt64 = (jsonString: any) => {
+ return parseWithBigInt(jsonString, isBigNumber);
+};
diff --git a/enshi/tailwind.config.js b/enshi/tailwind.config.js
index 76881c2..3ae45b8 100644
--- a/enshi/tailwind.config.js
+++ b/enshi/tailwind.config.js
@@ -1,21 +1,53 @@
/** @type {import('tailwindcss').Config} */
export default {
-content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {
- animation: {
- 'appear': 'appear 0.25s'
- },
- keyframes: {
- appear: {
- '100%': {opacity: '1'}
- }
- }
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
+ theme: {
+ extend: {
+ colors: {
+ "primary-color": "var(--primary-color)",
+ "secondary-color": "var(--secondary-color)",
+ },
+ fontFamily: {
+ 'times': "Times New Roman"
+ },
+ animation: {
+ appear: "appear 0.25s",
+ widthOut: "widthOut cubic-bezier(0.4, 0, 0.6, 1) 0.4s",
+ slideFromRight: "slideFromRight cubic-bezier(0.4, 0, 0.6, 1) 0.2s",
+ fadeOut: "fadeOut 0.2s ease-in",
+ },
+ keyframes: {
+ fadeOut: {
+ from: {
+ opacity: "1",
+ },
+ to: {
+ opacity: "0",
+ }
+ },
+ slideFromRight: {
+ "0%": {
+ transform: "translateX(110%)"
+ },
+ "100%": {
+ transform: "translateX(0%)"
+ }
+ },
+ appear: {
+ "100%": { opacity: "1" },
+ },
+ widthOut: {
+ "0%": {
+ width: "0%",
+ left: "50%",
+ },
+ "100%": {
+ width: "100%",
+ left: "0%",
+ },
+ },
+ },
+ },
},
- },
- plugins: [],
-}
-
+ plugins: [],
+};
diff --git a/enshi/test b/enshi/test
deleted file mode 100644
index e69de29..0000000
diff --git a/enshi/vite.config.ts b/enshi/vite.config.ts
index 5a33944..36f7f4e 100644
--- a/enshi/vite.config.ts
+++ b/enshi/vite.config.ts
@@ -1,5 +1,5 @@
-import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
diff --git a/enshi_back/.dockerignore b/enshi_back/.dockerignore
new file mode 100644
index 0000000..7d02313
--- /dev/null
+++ b/enshi_back/.dockerignore
@@ -0,0 +1,8 @@
+main
+*.log
+*.swp
+*.tmp
+*.out
+node_modules
+.idea
+.vscode
\ No newline at end of file
diff --git a/enshi_back/ABAC/AdminPolicies/AdminPolicy.go b/enshi_back/ABAC/AdminPolicies/AdminPolicy.go
new file mode 100644
index 0000000..00a91e7
--- /dev/null
+++ b/enshi_back/ABAC/AdminPolicies/AdminPolicy.go
@@ -0,0 +1,17 @@
+package adminpolicies
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func AdminPolicies(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsAdminRule,
+ }
+
+ return rules.CheckRules(c, rulesToCheck, rules.ALL_RULES_MUST_BE_COMPLETED)
+}
diff --git a/enshi_back/ABAC/BookmarkPolicies/bookmarkPolicies.go b/enshi_back/ABAC/BookmarkPolicies/bookmarkPolicies.go
new file mode 100644
index 0000000..fbae00d
--- /dev/null
+++ b/enshi_back/ABAC/BookmarkPolicies/bookmarkPolicies.go
@@ -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
+}
diff --git a/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/createRule.go b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/createRule.go
new file mode 100644
index 0000000..fdac869
--- /dev/null
+++ b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/createRule.go
@@ -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
+}
diff --git a/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/deleteRule.go b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/deleteRule.go
new file mode 100644
index 0000000..510b8ad
--- /dev/null
+++ b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/deleteRule.go
@@ -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
+}
diff --git a/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/readRule.go b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/readRule.go
new file mode 100644
index 0000000..ad888b6
--- /dev/null
+++ b/enshi_back/ABAC/BookmarkPolicies/bookmarkRules/readRule.go
@@ -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
+}
diff --git a/enshi_back/ABAC/GlobalRules/AuthorizedRule.go b/enshi_back/ABAC/GlobalRules/AuthorizedRule.go
new file mode 100644
index 0000000..63fe327
--- /dev/null
+++ b/enshi_back/ABAC/GlobalRules/AuthorizedRule.go
@@ -0,0 +1,30 @@
+package globalrules
+
+import (
+ "enshi/auth"
+ "enshi/global"
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+func AuthorizedRule(c *gin.Context) (bool, []error) {
+ cookies := c.Request.CookiesNamed("auth_cookie")
+ if len(cookies) == 0 {
+ return false, []error{fmt.Errorf("no cookies provided")}
+ }
+
+ tokenFromCookies := cookies[0].Value
+ cookieClimes, err := auth.ValidateToken(tokenFromCookies)
+ if err != nil {
+ c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
+ c.Abort()
+ return false, []error{err}
+ } else {
+ c.Set(global.ContextUserId, cookieClimes["id"])
+ c.Set(global.ContextTokenData, cookieClimes)
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/ABAC/GlobalRules/IsAdminRule.go b/enshi_back/ABAC/GlobalRules/IsAdminRule.go
new file mode 100644
index 0000000..d1596e5
--- /dev/null
+++ b/enshi_back/ABAC/GlobalRules/IsAdminRule.go
@@ -0,0 +1,33 @@
+package globalrules
+
+import (
+ "context"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+)
+
+func IsAdminRule(c *gin.Context) (bool, []error) {
+ contextUserId, err := getters.GetUserIdFromContext(c)
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ user, err :=
+ db_repo.New(db_connection.Dbx).
+ GetUserById(context.Background(), contextUserId)
+
+ if err != nil || user.UserID == 0 {
+ return false, []error{err}
+ }
+
+ if !user.IsAdmin {
+ return false, []error{fmt.Errorf("not admin")}
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/ABAC/GlobalRules/IsOwnerOfTheBlogRule.go b/enshi_back/ABAC/GlobalRules/IsOwnerOfTheBlogRule.go
new file mode 100644
index 0000000..e67ca75
--- /dev/null
+++ b/enshi_back/ABAC/GlobalRules/IsOwnerOfTheBlogRule.go
@@ -0,0 +1,39 @@
+package globalrules
+
+import (
+ "context"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+)
+
+func IsOwnerOfTheBlogRule(c *gin.Context) (bool, []error) {
+ blogId, err := getters.GetInt64Param(c, "blog-id")
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ contextUserId, err := getters.GetUserIdFromContext(c)
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ blog, err :=
+ db_repo.New(db_connection.Dbx).
+ GetBlogByBlogId(context.Background(), blogId)
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ if blog.UserID != contextUserId {
+ return false, []error{fmt.Errorf("now owner of the blog")}
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/ABAC/GlobalRules/IsOwnerOfThePostRule.go b/enshi_back/ABAC/GlobalRules/IsOwnerOfThePostRule.go
new file mode 100644
index 0000000..af25324
--- /dev/null
+++ b/enshi_back/ABAC/GlobalRules/IsOwnerOfThePostRule.go
@@ -0,0 +1,39 @@
+package globalrules
+
+import (
+ "context"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+)
+
+func IsOwnerOfThePostRule(c *gin.Context) (bool, []error) {
+ postId, err := getters.GetInt64Param(c, "post-id")
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ contextUserId, err := getters.GetUserIdFromContext(c)
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ post, err :=
+ db_repo.New(db_connection.Dbx).
+ GetPostsByPostId(context.Background(), postId)
+
+ if err != nil {
+ return false, []error{err}
+ }
+
+ if post.UserID != contextUserId {
+ return false, []error{fmt.Errorf("now owner of the post")}
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go b/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go
new file mode 100644
index 0000000..9e597bf
--- /dev/null
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVotePolicies.go
@@ -0,0 +1,38 @@
+package postvotespolicies
+
+import (
+ postvoterules "enshi/ABAC/PostVotesPolicies/PostVoteRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ DELETE_VOTE = "delete_vote"
+ CREATE_VOTE = "create_vote"
+ READ_VOTE = "read_vote"
+)
+
+func PostVotePolicies(c *gin.Context) (bool, []error) {
+ target, exists := c.Get("target")
+ if !exists {
+ return false, nil
+ }
+
+ // Permit if one permit
+ switch target {
+ case DELETE_VOTE:
+ return rules.CheckRule(c, postvoterules.PostVoteDeleteRule)
+
+ case CREATE_VOTE:
+ return rules.CheckRule(c, postvoterules.PostVoteCreateRule)
+
+ 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/createRule.go b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/createRule.go
new file mode 100644
index 0000000..cdde23a
--- /dev/null
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/createRule.go
@@ -0,0 +1,22 @@
+package postvoterules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func PostVoteCreateRule(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
+}
diff --git a/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/deleteRule.go b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/deleteRule.go
new file mode 100644
index 0000000..b24ecc9
--- /dev/null
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/deleteRule.go
@@ -0,0 +1,22 @@
+package postvoterules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func PostVoteDeleteRule(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
+}
diff --git a/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readRule.go b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readRule.go
new file mode 100644
index 0000000..0f707cc
--- /dev/null
+++ b/enshi_back/ABAC/PostVotesPolicies/PostVoteRules/readRule.go
@@ -0,0 +1,22 @@
+package postvoterules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func PostVoteReadRule(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
+}
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/ABAC/PostsPolicies/postPolicy.go b/enshi_back/ABAC/PostsPolicies/postPolicy.go
new file mode 100644
index 0000000..6e7e542
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postPolicy.go
@@ -0,0 +1,48 @@
+package postspolicies
+
+import (
+ "enshi/ABAC/PostsPolicies/postRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ DELETE_POST = "delete_post"
+ DELETE_POST_BLOG = "delete_post_blog"
+ UPDATE_POST = "update_post"
+ UPDATE_POST_BLOG = "update_post_blog"
+ CREATE_POST = "create_post"
+ GET_POST = "get_post"
+)
+
+func PostsPolicies(c *gin.Context) (bool, []error) {
+ target, exists := c.Get("target")
+ if !exists {
+ return false, nil
+ }
+
+ // Permit if one permit
+ switch target {
+ case DELETE_POST:
+ return rules.CheckRule(c, postRules.DeleteRule)
+
+ case DELETE_POST_BLOG:
+ return rules.CheckRule(c, postRules.DeletePostFromBlogRule)
+
+ case UPDATE_POST:
+ return rules.CheckRule(c, postRules.PostUpdateRule)
+
+ case UPDATE_POST_BLOG:
+ return rules.CheckRule(c, postRules.UpdatePostBlogRule)
+
+ case GET_POST:
+ return rules.CheckRule(c, postRules.PostReadRule)
+
+ case CREATE_POST:
+ return rules.CheckRule(c, postRules.PostCreateRule)
+
+ }
+
+ return false, nil
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/createRule.go b/enshi_back/ABAC/PostsPolicies/postRules/createRule.go
new file mode 100644
index 0000000..b998d5c
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/createRule.go
@@ -0,0 +1,23 @@
+package postRules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Only owner of the post can change it
+func PostCreateRule(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
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/deletePostFromBlogRule.go b/enshi_back/ABAC/PostsPolicies/postRules/deletePostFromBlogRule.go
new file mode 100644
index 0000000..d99c10c
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/deletePostFromBlogRule.go
@@ -0,0 +1,24 @@
+package postRules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func DeletePostFromBlogRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ RULES_NUMBER_TO_COMPLETE,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/deleteRule.go b/enshi_back/ABAC/PostsPolicies/postRules/deleteRule.go
new file mode 100644
index 0000000..1e71cb6
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/deleteRule.go
@@ -0,0 +1,27 @@
+package postRules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+const RULES_NUMBER_TO_COMPLETE = 2
+
+// Only owner or admin can delete post
+func DeleteRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsAdminRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ RULES_NUMBER_TO_COMPLETE,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/readRule.go b/enshi_back/ABAC/PostsPolicies/postRules/readRule.go
new file mode 100644
index 0000000..922b45e
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/readRule.go
@@ -0,0 +1,10 @@
+package postRules
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+// Only owner of the post can change it
+func PostReadRule(c *gin.Context) (bool, []error) {
+ return true, nil
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/updatePostBlogRule.go b/enshi_back/ABAC/PostsPolicies/postRules/updatePostBlogRule.go
new file mode 100644
index 0000000..887c784
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/updatePostBlogRule.go
@@ -0,0 +1,25 @@
+package postRules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Only user that own target post and blog can do that
+func UpdatePostBlogRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ rules.ALL_RULES_MUST_BE_COMPLETED,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/PostsPolicies/postRules/updateRule.go b/enshi_back/ABAC/PostsPolicies/postRules/updateRule.go
new file mode 100644
index 0000000..9e04db0
--- /dev/null
+++ b/enshi_back/ABAC/PostsPolicies/postRules/updateRule.go
@@ -0,0 +1,24 @@
+package postRules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Only owner of the post can change it
+func PostUpdateRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ rules.ALL_RULES_MUST_BE_COMPLETED,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/ProfilePolicies/ProfilePolicies.go b/enshi_back/ABAC/ProfilePolicies/ProfilePolicies.go
new file mode 100644
index 0000000..cee74e1
--- /dev/null
+++ b/enshi_back/ABAC/ProfilePolicies/ProfilePolicies.go
@@ -0,0 +1,31 @@
+package profilepolicies
+
+import (
+ profilesrules "enshi/ABAC/ProfilePolicies/ProfilesRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ RESET_PROFILE = "reset_profile"
+ UPDATE_PROFILE = "update_profile"
+ CREATE_PROFILE = "create_profile"
+ GET_PROFILE = "get_profile"
+)
+
+func ProfilePolicies(c *gin.Context) (bool, []error) {
+ target, exists := c.Get("target")
+ if !exists {
+ return false, nil
+ }
+
+ // Permit if one permit
+ switch target {
+ case UPDATE_PROFILE:
+ return rules.CheckRule(c, profilesrules.UpdateProfileRule)
+
+ }
+
+ return false, nil
+}
diff --git a/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go b/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go
new file mode 100644
index 0000000..f90f857
--- /dev/null
+++ b/enshi_back/ABAC/ProfilePolicies/ProfilesRules/UpdateRule.go
@@ -0,0 +1,22 @@
+package profilesrules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func UpdateProfileRule(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
+}
diff --git a/enshi_back/ABAC/blogsPolicies/blogPolicies.go b/enshi_back/ABAC/blogsPolicies/blogPolicies.go
new file mode 100644
index 0000000..e9dcfff
--- /dev/null
+++ b/enshi_back/ABAC/blogsPolicies/blogPolicies.go
@@ -0,0 +1,40 @@
+package blogspolicies
+
+import (
+ blogrules "enshi/ABAC/blogsPolicies/blogRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ DELETE_BLOG = "delete_blog"
+ UPDATE_BLOG = "update_blog"
+ CREATE_BLOG = "create_blog"
+ GET_BLOG = "get_blog"
+)
+
+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_BLOG:
+ return rules.CheckRule(c, blogrules.BlogDeleteRule)
+
+ case UPDATE_BLOG:
+ return rules.CheckRule(c, blogrules.BlogUpdateRule)
+
+ case GET_BLOG:
+ return rules.CheckRule(c, blogrules.BlogReadRule)
+
+ case CREATE_BLOG:
+ return rules.CheckRule(c, blogrules.BlogCreateRule)
+
+ }
+
+ return false, nil
+}
diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go
new file mode 100644
index 0000000..c973353
--- /dev/null
+++ b/enshi_back/ABAC/blogsPolicies/blogRules/createRule.go
@@ -0,0 +1,22 @@
+package blogrules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func BlogCreateRule(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
+}
diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/deleteRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/deleteRule.go
new file mode 100644
index 0000000..5c4f024
--- /dev/null
+++ b/enshi_back/ABAC/blogsPolicies/blogRules/deleteRule.go
@@ -0,0 +1,24 @@
+package blogrules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func BlogDeleteRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ globalrules.IsAdminRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ 2,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go
new file mode 100644
index 0000000..6658687
--- /dev/null
+++ b/enshi_back/ABAC/blogsPolicies/blogRules/readRule.go
@@ -0,0 +1,19 @@
+package blogrules
+
+import (
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func BlogReadRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{}
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ rules.ALL_RULES_MUST_BE_COMPLETED,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go b/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go
new file mode 100644
index 0000000..cb6ab72
--- /dev/null
+++ b/enshi_back/ABAC/blogsPolicies/blogRules/updateRule.go
@@ -0,0 +1,23 @@
+package blogrules
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func BlogUpdateRule(c *gin.Context) (bool, []error) {
+ rulesToCheck := []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ }
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ rules.ALL_RULES_MUST_BE_COMPLETED,
+ )
+
+ return isAllowed, errors
+}
diff --git a/enshi_back/ABAC/rules/CheckRule.go b/enshi_back/ABAC/rules/CheckRule.go
new file mode 100644
index 0000000..6147efc
--- /dev/null
+++ b/enshi_back/ABAC/rules/CheckRule.go
@@ -0,0 +1,72 @@
+package rules
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+)
+
+type RuleFunction func(*gin.Context) (bool, []error)
+
+const (
+ ALL_RULES_MUST_BE_COMPLETED = iota
+)
+
+func CheckRule(
+ c *gin.Context,
+ ruleChecker RuleFunction,
+) (bool, []error) {
+ IsAllowed, err := ruleChecker(c)
+ if err != nil {
+ return false, err
+ }
+
+ return IsAllowed, nil
+}
+
+func CheckRules(
+ c *gin.Context,
+ rules []RuleFunction,
+ completedRulesCount int,
+) (bool, []error) {
+ var allowancesIndexes []int
+ var errors []error
+
+ if len(rules) < completedRulesCount {
+ return false, []error{fmt.Errorf("there is less rules, that should be completed")}
+ }
+
+ for i, rule := range rules {
+ if isAllowed, err := CheckRule(c, rule); err != nil {
+ errors = append(
+ errors,
+ err...,
+ )
+ } else if !isAllowed {
+ errors = append(
+ errors,
+ fmt.Errorf("rule "+
+ strconv.Itoa(i)+
+ " was rejected"),
+ )
+ } else {
+ allowancesIndexes = append(allowancesIndexes, i)
+ }
+ }
+
+ switch completedRulesCount {
+ case ALL_RULES_MUST_BE_COMPLETED:
+ if len(allowancesIndexes) == len(rules) {
+ return true, nil
+ } else {
+ return false, errors
+ }
+ default:
+ if len(allowancesIndexes) >= completedRulesCount {
+ return true, nil
+ } else {
+ return false, errors
+ }
+ }
+}
diff --git a/enshi_back/ABAC/rules/ShouldAbortRequest.go b/enshi_back/ABAC/rules/ShouldAbortRequest.go
new file mode 100644
index 0000000..ee5b804
--- /dev/null
+++ b/enshi_back/ABAC/rules/ShouldAbortRequest.go
@@ -0,0 +1,28 @@
+package rules
+
+import (
+ rest_api_stuff "enshi/REST_API_stuff"
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+func ShouldAbortRequest(c *gin.Context, isAllowed bool, errors []error) bool {
+ var errorsMap = map[int]string{}
+ for i, error := range errors {
+ errorsMap[i] = error.Error()
+ }
+
+ if errors != nil {
+ c.IndentedJSON(http.StatusUnauthorized, errorsMap)
+ return true
+ }
+
+ if !isAllowed {
+ rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you have no permission"))
+ return true
+ }
+
+ return false
+}
diff --git a/enshi_back/db/go_queries/blogs_queries.sql.go b/enshi_back/db/go_queries/blogs_queries.sql.go
index ecf6c80..3029ce9 100644
--- a/enshi_back/db/go_queries/blogs_queries.sql.go
+++ b/enshi_back/db/go_queries/blogs_queries.sql.go
@@ -21,7 +21,7 @@ RETURNING blog_id, user_id, title, description, category_id, created_at
type CreateBlogByUserIdParams struct {
BlogID int64 `json:"blog_id"`
UserID int64 `json:"user_id"`
- Title pgtype.Text `json:"title"`
+ Title pgtype.Text `json:"title" validate:"required"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
}
@@ -56,6 +56,26 @@ func (q *Queries) DeleteBlogByBlogId(ctx context.Context, blogID int64) error {
return err
}
+const getBlogByBlogId = `-- name: GetBlogByBlogId :one
+SELECT blog_id, user_id, title, description, category_id, created_at
+FROM public.blogs
+WHERE blog_id = $1
+`
+
+func (q *Queries) GetBlogByBlogId(ctx context.Context, blogID int64) (Blog, error) {
+ row := q.db.QueryRow(ctx, getBlogByBlogId, blogID)
+ var i Blog
+ err := row.Scan(
+ &i.BlogID,
+ &i.UserID,
+ &i.Title,
+ &i.Description,
+ &i.CategoryID,
+ &i.CreatedAt,
+ )
+ return i, err
+}
+
const getBlogsByUserId = `-- name: GetBlogsByUserId :many
SELECT blog_id, user_id, title, description, category_id, created_at
FROM public.blogs
@@ -97,7 +117,7 @@ RETURNING blog_id, user_id, title, description, category_id, created_at
`
type UpdateBlogInfoByBlogIdParams struct {
- Title pgtype.Text `json:"title"`
+ Title pgtype.Text `json:"title" validate:"required"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
BlogID int64 `json:"blog_id"`
diff --git a/enshi_back/db/go_queries/models.go b/enshi_back/db/go_queries/models.go
index 7d227a9..6438133 100644
--- a/enshi_back/db/go_queries/models.go
+++ b/enshi_back/db/go_queries/models.go
@@ -11,7 +11,7 @@ import (
type Blog struct {
BlogID int64 `json:"blog_id"`
UserID int64 `json:"user_id"`
- Title pgtype.Text `json:"title"`
+ Title pgtype.Text `json:"title" validate:"required"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
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 171b179..05e957c 100644
--- a/enshi_back/db/go_queries/post_votes_queries.sql.go
+++ b/enshi_back/db/go_queries/post_votes_queries.sql.go
@@ -13,6 +13,9 @@ const createPostVote = `-- name: CreatePostVote :one
INSERT INTO public.post_votes
(post_id, user_id, vote)
VALUES($1, $2, $3)
+ON CONFLICT (user_id, post_id)
+DO UPDATE SET
+ vote = $3
RETURNING post_id, user_id, vote
`
@@ -62,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/go_queries/posts_queries.sql.go b/enshi_back/db/go_queries/posts_queries.sql.go
index ba88faa..c778e15 100644
--- a/enshi_back/db/go_queries/posts_queries.sql.go
+++ b/enshi_back/db/go_queries/posts_queries.sql.go
@@ -146,29 +146,79 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
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
+UPDATE public.posts
+SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
+WHERE post_id = $1
+RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
+`
+
+type UpdatePostBlogIdParams struct {
+ PostID int64 `json:"post_id"`
+ BlogID pgtype.Int8 `json:"blog_id"`
+}
+
+func (q *Queries) UpdatePostBlogId(ctx context.Context, arg UpdatePostBlogIdParams) error {
+ _, err := q.db.Exec(ctx, updatePostBlogId, arg.PostID, arg.BlogID)
+ return err
+}
+
const updatePostByPostId = `-- name: UpdatePostByPostId :one
UPDATE public.posts
-SET blog_id=$1, user_id=$2, title=$3, "content"=$4, updated_at=CURRENT_TIMESTAMP
-WHERE post_id = $5
+SET title=$1, "content"=$2, updated_at=CURRENT_TIMESTAMP
+WHERE post_id = $3
RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
`
type UpdatePostByPostIdParams struct {
- BlogID pgtype.Int8 `json:"blog_id"`
- UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
PostID int64 `json:"post_id"`
}
func (q *Queries) UpdatePostByPostId(ctx context.Context, arg UpdatePostByPostIdParams) (Post, error) {
- row := q.db.QueryRow(ctx, updatePostByPostId,
- arg.BlogID,
- arg.UserID,
- arg.Title,
- arg.Content,
- arg.PostID,
- )
+ row := q.db.QueryRow(ctx, updatePostByPostId, arg.Title, arg.Content, arg.PostID)
var i Post
err := row.Scan(
&i.PostID,
diff --git a/enshi_back/db/go_queries/users_queries.sql.go b/enshi_back/db/go_queries/users_queries.sql.go
index de0e1d3..2660956 100644
--- a/enshi_back/db/go_queries/users_queries.sql.go
+++ b/enshi_back/db/go_queries/users_queries.sql.go
@@ -152,6 +152,17 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User,
return i, err
}
+const getUserUsernameById = `-- name: GetUserUsernameById :one
+SELECT username FROM users WHERE user_id = $1
+`
+
+func (q *Queries) GetUserUsernameById(ctx context.Context, userID int64) (string, error) {
+ row := q.db.QueryRow(ctx, getUserUsernameById, userID)
+ var username string
+ err := row.Scan(&username)
+ return username, err
+}
+
const updateUserPasswordHash = `-- name: UpdateUserPasswordHash :one
UPDATE public.users
SET "password"=$1
diff --git a/enshi_back/db/queries/blogs_queries.sql b/enshi_back/db/queries/blogs_queries.sql
index e47614b..cb5c575 100644
--- a/enshi_back/db/queries/blogs_queries.sql
+++ b/enshi_back/db/queries/blogs_queries.sql
@@ -15,6 +15,11 @@ SELECT *
FROM public.blogs
WHERE user_id = $1;
+-- name: GetBlogByBlogId :one
+SELECT *
+FROM public.blogs
+WHERE blog_id = $1;
+
-- name: DeleteBlogByBlogId :exec
DELETE FROM public.blogs
WHERE blog_id=$1;
\ No newline at end of file
diff --git a/enshi_back/db/queries/post_votes_queries.sql b/enshi_back/db/queries/post_votes_queries.sql
index 74cbfd3..ea1443a 100644
--- a/enshi_back/db/queries/post_votes_queries.sql
+++ b/enshi_back/db/queries/post_votes_queries.sql
@@ -2,6 +2,9 @@
INSERT INTO public.post_votes
(post_id, user_id, vote)
VALUES($1, $2, $3)
+ON CONFLICT (user_id, post_id)
+DO UPDATE SET
+ vote = $3
RETURNING *;
-- name: DeletePostVote :exec
@@ -17,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/db/queries/posts_queries.sql b/enshi_back/db/queries/posts_queries.sql
index c8e5545..9f22b8f 100644
--- a/enshi_back/db/queries/posts_queries.sql
+++ b/enshi_back/db/queries/posts_queries.sql
@@ -21,10 +21,22 @@ RETURNING *;
-- name: UpdatePostByPostId :one
UPDATE public.posts
-SET blog_id=$1, user_id=$2, title=$3, "content"=$4, updated_at=CURRENT_TIMESTAMP
-WHERE post_id = $5
+SET title=$1, "content"=$2, updated_at=CURRENT_TIMESTAMP
+WHERE post_id = $3
RETURNING *;
-- name: DeletePostByPostId :exec
DELETE FROM public.posts
-WHERE post_id=$1;
\ No newline at end of file
+WHERE post_id=$1;
+
+-- name: UpdatePostBlogId :exec
+UPDATE public.posts
+SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
+WHERE post_id = $1
+RETURNING *;
+
+-- name: GetRandomPosts :many
+SELECT post_id, blog_id, user_id, title, created_at
+FROM public.posts
+ORDER BY RANDOM()
+LIMIT $1;
\ No newline at end of file
diff --git a/enshi_back/db/queries/users_queries.sql b/enshi_back/db/queries/users_queries.sql
index fcf2f59..1c84a51 100644
--- a/enshi_back/db/queries/users_queries.sql
+++ b/enshi_back/db/queries/users_queries.sql
@@ -4,6 +4,9 @@ SELECT * FROM users;
-- name: GetUserById :one
SELECT * FROM users WHERE user_id = $1;
+-- name: GetUserUsernameById :one
+SELECT username FROM users WHERE user_id = $1;
+
-- name: GetUserByUsername :one
SELECT * FROM users WHERE username = $1;
diff --git a/enshi_back/db/sqlc.yml b/enshi_back/db/sqlc.yml
index cdfcea3..1eb5d5f 100644
--- a/enshi_back/db/sqlc.yml
+++ b/enshi_back/db/sqlc.yml
@@ -18,6 +18,9 @@ sql:
- column: users.email
go_struct_tag: validate:"required,email"
+ - column: blogs.title
+ go_struct_tag: validate:"required"
+
- db_type: "uuid"
go_type:
import: 'github.com/google/uuid'
diff --git a/enshi_back/dockerfile b/enshi_back/dockerfile
new file mode 100644
index 0000000..03b4638
--- /dev/null
+++ b/enshi_back/dockerfile
@@ -0,0 +1,15 @@
+FROM golang:1.23.3-alpine3.20
+
+WORKDIR /enshi_app
+
+COPY go.mod go.sum ./
+
+RUN go mod download
+
+COPY . .
+
+RUN go build -o enshi_bin .
+
+EXPOSE 9876
+
+CMD [ "./enshi_bin" ]
\ No newline at end of file
diff --git a/enshi_back/global/globalColorsForConsole.go b/enshi_back/global/globalColorsForConsole.go
index 1de4c21..e7ea966 100644
--- a/enshi_back/global/globalColorsForConsole.go
+++ b/enshi_back/global/globalColorsForConsole.go
@@ -1,11 +1,11 @@
package global
-var ResetColor = "\033[0m"
-var RedColor = "\033[31m"
-var GreenColor = "\033[32m"
-var YellowColor = "\033[33m"
-var BlueColor = "\033[34m"
-var MagentaColor = "\033[35m"
-var CyanColor = "\033[36m"
-var GrayColor = "\033[37m"
-var WhiteColor = "\033[97m"
+const ResetColor = "\033[0m"
+const RedColor = "\033[31m"
+const GreenColor = "\033[32m"
+const YellowColor = "\033[33m"
+const BlueColor = "\033[34m"
+const MagentaColor = "\033[35m"
+const CyanColor = "\033[36m"
+const GrayColor = "\033[37m"
+const WhiteColor = "\033[97m"
diff --git a/enshi_back/global/globalVars.go b/enshi_back/global/globalVars.go
index b064789..fae122b 100644
--- a/enshi_back/global/globalVars.go
+++ b/enshi_back/global/globalVars.go
@@ -1,6 +1,32 @@
package global
+import (
+ "fmt"
+ "os"
+)
+
var PathForCookies = "/"
-var DomainForCookies = "localhost"
-var SecureForCookies = false
-var HttpOnlyForCookies = false
+var DomainForCookies = "127.0.0.1"
+
+const SecureForCookies = false
+const HttpOnlyForCookies = false
+
+// Change to 0.0.0.0 when docker this
+const GinWorkPath = "127.0.0.1:9876"
+
+func GetGinWorkPath() string {
+
+ if os.Getenv("DOMAIN") != "" {
+ DomainForCookies = os.Getenv("DOMAIN")
+ PathForCookies = "/api/v1/"
+ fmt.Println("DomainForCookies is", DomainForCookies)
+ }
+
+ if os.Getenv("ENV") == "docker" {
+ fmt.Println("GinWorkPath is docker 0.0.0.0:9876")
+ return "0.0.0.0:9876"
+ }
+
+ fmt.Println("GinWorkPath is local 127.0.0.1:9876")
+ return GinWorkPath
+}
diff --git a/enshi_back/global/keys.go b/enshi_back/global/keys.go
new file mode 100644
index 0000000..902798e
--- /dev/null
+++ b/enshi_back/global/keys.go
@@ -0,0 +1,5 @@
+package global
+
+const ContextUserId = "id"
+const ContextIsAdmin = "isAdmin"
+const ContextTokenData = "tokenData"
diff --git a/enshi_back/main.go b/enshi_back/main.go
index 72320f0..ccc8a2c 100644
--- a/enshi_back/main.go
+++ b/enshi_back/main.go
@@ -5,8 +5,12 @@ import (
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/env"
- utils "enshi/utils"
+ "enshi/global"
+ "enshi/routes"
"fmt"
+ "io"
+ "log"
+ "os"
"github.com/gin-gonic/gin"
)
@@ -26,12 +30,20 @@ func main() {
defer db_connection.Dbx_connection.Close(context.Background())
router := gin.Default()
- if err := utils.SetupRotes(router); err != nil {
+
+ f, err := os.OpenFile("gin.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ gin.DefaultWriter = io.MultiWriter(f)
+
+ if err := routes.SetupRotes(router); err != nil {
fmt.Println(err.Error())
return
}
- // Transaction
+ // Test Transaction
tx, _ := db_connection.Dbx.Begin(context.Background())
defer tx.Rollback(context.Background())
@@ -47,7 +59,7 @@ func main() {
return
}
- router.Run("localhost:9876")
+ router.Run(global.GetGinWorkPath())
fmt.Printf("Hey!, %v", "you")
}
diff --git a/enshi_back/middleware/MiddlewareProvider.go b/enshi_back/middleware/MiddlewareProvider.go
new file mode 100644
index 0000000..fc11dbf
--- /dev/null
+++ b/enshi_back/middleware/MiddlewareProvider.go
@@ -0,0 +1,118 @@
+package middleware
+
+import (
+ "enshi/ABAC/rules"
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+)
+
+type WorkRule struct {
+ Rules []rules.RuleFunction
+ MustBeCompleted int
+}
+
+type Policy func(c *gin.Context) (bool, []error)
+type RuleSets map[string]rules.RuleFunction
+type RulesToCheck map[string]WorkRule
+
+type MiddlewareProvider struct {
+ Policies map[string]Policy
+}
+
+func CreateRuleFunction(rulesToCheck []rules.RuleFunction, mustBeCompleted int) rules.RuleFunction {
+ return func(c *gin.Context) (bool, []error) {
+
+ isAllowed, errors := rules.CheckRules(
+ c,
+ rulesToCheck,
+ mustBeCompleted,
+ )
+
+ return isAllowed, errors
+ }
+}
+
+func CreatePolicy(ruleSets RuleSets) Policy {
+
+ return func(c *gin.Context) (bool, []error) {
+ targetAction, exists := c.Get("target")
+ if !exists {
+ return false, nil
+ }
+
+ for action, rule := range ruleSets {
+ if action == targetAction {
+ return rules.CheckRule(c, rule)
+ }
+ }
+
+ return false, nil
+ }
+
+}
+
+// Accepts
+//
+// ruleSetName -> `string` name of the policy(like old one "postPolicy" etc.)
+//
+// rulesToCheck -> map where keys like ["GET", "POST", etc.] and values are struct of type {rules: [list of rules to check], mustBeCompleted: how many rules must be completed from the list before}
+func (m *MiddlewareProvider) RegisterPolicy(
+ ruleSetName string,
+ rulesToCheck RulesToCheck,
+) error {
+
+ for k := range m.Policies {
+ if k == ruleSetName {
+ return fmt.Errorf("name: " + ruleSetName + " already exists")
+ }
+ }
+
+ newRuleSets := make(RuleSets)
+ for setName, workRule := range rulesToCheck {
+ newRuleFunction := CreateRuleFunction(workRule.Rules, workRule.MustBeCompleted)
+ newRuleSets[setName] = newRuleFunction
+ }
+
+ newPolicy := CreatePolicy(newRuleSets)
+
+ m.Policies[ruleSetName] = newPolicy
+
+ return nil
+}
+
+func (m *MiddlewareProvider) GetMiddleware(
+ policyName string,
+) gin.HandlerFunc {
+
+ return func(c *gin.Context) {
+
+ validName := false
+ for key := range m.Policies {
+ if key == policyName {
+ validName = true
+ }
+ }
+ if !validName {
+ c.Abort()
+ fmt.Println("invalid policy name: " + policyName)
+ return
+ }
+
+ isAllowed, errors := m.Policies[policyName](c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+
+}
+
+func (m *MiddlewareProvider) InitMiddlewareProvider(policies map[string]RulesToCheck) {
+ for middlewareName, rulesToCheck := range policies {
+ m.RegisterPolicy(middlewareName, rulesToCheck)
+ }
+}
diff --git a/enshi_back/middleware/ProfileMiddleware.go b/enshi_back/middleware/ProfileMiddleware.go
new file mode 100644
index 0000000..36877d5
--- /dev/null
+++ b/enshi_back/middleware/ProfileMiddleware.go
@@ -0,0 +1,26 @@
+package middleware
+
+import (
+ profilepolicies "enshi/ABAC/ProfilePolicies"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func ProfileMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ switch c.Request.Method {
+ case "PUT":
+ c.Set("target", profilepolicies.UPDATE_PROFILE)
+ }
+
+ isAllowed, errors := profilepolicies.ProfilePolicies(c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/enshi_back/middleware/TargetMiddleware.go b/enshi_back/middleware/TargetMiddleware.go
new file mode 100644
index 0000000..ea7d3ac
--- /dev/null
+++ b/enshi_back/middleware/TargetMiddleware.go
@@ -0,0 +1,30 @@
+package middleware
+
+import (
+ "github.com/gin-gonic/gin"
+)
+
+const (
+ GET = "GET"
+ PUT = "PUT"
+ POST = "POST"
+ DELETE = "DELETE"
+)
+
+func TargetMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+
+ switch c.Request.Method {
+ case "DELETE":
+ c.Set("target", DELETE)
+ case "PUT":
+ c.Set("target", PUT)
+ case "POST":
+ c.Set("target", POST)
+ case "GET":
+ c.Set("target", DELETE)
+ }
+
+ c.Next()
+ }
+}
diff --git a/enshi_back/middleware/adminMiddleware.go b/enshi_back/middleware/adminMiddleware.go
index d336e0b..51841d6 100644
--- a/enshi_back/middleware/adminMiddleware.go
+++ b/enshi_back/middleware/adminMiddleware.go
@@ -1,9 +1,20 @@
package middleware
-import "github.com/gin-gonic/gin"
+import (
+ adminpolicies "enshi/ABAC/AdminPolicies"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
+ isAllowed, errors := adminpolicies.AdminPolicies(c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
c.Next()
}
diff --git a/enshi_back/middleware/authMiddleware.go b/enshi_back/middleware/authMiddleware.go
index d99a6df..e738ad3 100644
--- a/enshi_back/middleware/authMiddleware.go
+++ b/enshi_back/middleware/authMiddleware.go
@@ -1,7 +1,10 @@
package middleware
import (
+ rest_api_stuff "enshi/REST_API_stuff"
"enshi/auth"
+ "enshi/global"
+ "fmt"
"net/http"
"github.com/gin-gonic/gin"
@@ -10,9 +13,14 @@ import (
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
- // token := c.GetHeader("Authorization")
+ cookies := c.Request.CookiesNamed("auth_cookie")
+ if len(cookies) == 0 {
+ rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("no token provided"))
+ c.Abort()
+ return
+ }
- tokenFromCookies := c.Request.CookiesNamed("auth_cookie")[0].Value
+ tokenFromCookies := cookies[0].Value
cookieClimes, err := auth.ValidateToken(tokenFromCookies)
if err != nil {
c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
@@ -20,16 +28,8 @@ func AuthMiddleware() gin.HandlerFunc {
return
}
- // claims, err := auth.ValidateToken(token)
- // if err != nil {
- // c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
- // c.Abort()
- // return
- // }
-
- // Claims -> data stored in token
- c.Set(ContextUserId, cookieClimes["id"])
- c.Set(ContextTokenData, cookieClimes)
+ c.Set(global.ContextUserId, cookieClimes["id"])
+ c.Set(global.ContextTokenData, cookieClimes)
c.Next()
}
diff --git a/enshi_back/middleware/blogsMiddleware.go b/enshi_back/middleware/blogsMiddleware.go
new file mode 100644
index 0000000..eeb58dc
--- /dev/null
+++ b/enshi_back/middleware/blogsMiddleware.go
@@ -0,0 +1,33 @@
+package middleware
+
+import (
+ blogspolicies "enshi/ABAC/blogsPolicies"
+ "enshi/ABAC/rules"
+
+ "github.com/gin-gonic/gin"
+)
+
+func BlogsMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+
+ switch c.Request.Method {
+ case "DELETE":
+ c.Set("target", blogspolicies.DELETE_BLOG)
+ case "PUT":
+ c.Set("target", blogspolicies.UPDATE_BLOG)
+ case "POST":
+ c.Set("target", blogspolicies.CREATE_BLOG)
+ case "GET":
+ c.Set("target", blogspolicies.GET_BLOG)
+ }
+
+ isAllowed, errors := blogspolicies.BlogPolicies(c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/enshi_back/middleware/bookmarksMiddleware.go b/enshi_back/middleware/bookmarksMiddleware.go
new file mode 100644
index 0000000..b2ca48a
--- /dev/null
+++ b/enshi_back/middleware/bookmarksMiddleware.go
@@ -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()
+ }
+}
diff --git a/enshi_back/middleware/checkRole/isAdmin.go b/enshi_back/middleware/checkRole/isAdmin.go
new file mode 100644
index 0000000..4e16a59
--- /dev/null
+++ b/enshi_back/middleware/checkRole/isAdmin.go
@@ -0,0 +1,32 @@
+package checkRole
+
+import (
+ "context"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+
+ "github.com/gin-gonic/gin"
+)
+
+func IsAdmin(c *gin.Context) (bool, error) {
+ userId, err := getters.GetUserIdFromContext(c)
+
+ if err != nil {
+ return false, err
+ }
+
+ user, err :=
+ db_repo.New(db_connection.Dbx).
+ GetUserById(context.Background(), userId)
+
+ if err != nil || user.UserID == 0 {
+ return false, err
+ }
+
+ if !user.IsAdmin {
+ return false, nil
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/middleware/checkRole/isOwnerOfThePost.go b/enshi_back/middleware/checkRole/isOwnerOfThePost.go
new file mode 100644
index 0000000..58cc5b5
--- /dev/null
+++ b/enshi_back/middleware/checkRole/isOwnerOfThePost.go
@@ -0,0 +1,38 @@
+package checkRole
+
+import (
+ "context"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+
+ "github.com/gin-gonic/gin"
+)
+
+func IsOwnerOfThePost(c *gin.Context) (bool, error) {
+ postId, err := getters.GetInt64Param(c, "post-id")
+
+ if err != nil {
+ return false, err
+ }
+
+ userId, err := getters.GetUserIdFromContext(c)
+
+ if err != nil {
+ return false, err
+ }
+
+ post, err :=
+ db_repo.New(db_connection.Dbx).
+ GetPostsByPostId(context.Background(), postId)
+
+ if err != nil {
+ return false, err
+ }
+
+ if post.UserID != userId {
+ return false, nil
+ }
+
+ return true, nil
+}
diff --git a/enshi_back/middleware/corsMiddleware.go b/enshi_back/middleware/corsMiddleware.go
index ad20f2d..d0b2b8a 100644
--- a/enshi_back/middleware/corsMiddleware.go
+++ b/enshi_back/middleware/corsMiddleware.go
@@ -4,7 +4,7 @@ import "github.com/gin-gonic/gin"
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
- c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
+ c.Writer.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set(
"Access-Control-Allow-Headers",
diff --git a/enshi_back/middleware/getters/GetContextPayload.go b/enshi_back/middleware/getters/GetContextPayload.go
new file mode 100644
index 0000000..84c4dd9
--- /dev/null
+++ b/enshi_back/middleware/getters/GetContextPayload.go
@@ -0,0 +1,21 @@
+package getters
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/go-playground/validator/v10"
+)
+
+func GetContextPayload[T any](c *gin.Context) (T, error) {
+ var params T
+
+ if err := c.BindJSON(¶ms); err != nil {
+ return params, err
+ }
+
+ validate := validator.New(validator.WithRequiredStructEnabled())
+ if err := validate.Struct(params); err != nil {
+ return params, err
+ }
+
+ return params, nil
+}
diff --git a/enshi_back/middleware/getters/claims.go b/enshi_back/middleware/getters/claims.go
index c074cd4..19d170d 100644
--- a/enshi_back/middleware/getters/claims.go
+++ b/enshi_back/middleware/getters/claims.go
@@ -3,7 +3,6 @@ package getters
import (
"enshi/auth"
"enshi/global"
- "enshi/middleware"
"fmt"
"strconv"
@@ -14,7 +13,7 @@ import (
func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
var UserInfo auth.UserInfoJWT
- claims, exists := c.Get(middleware.ContextTokenData)
+ claims, exists := c.Get(global.ContextTokenData)
if !exists {
return auth.UserInfoJWT{}, fmt.Errorf("error getting user id")
@@ -31,13 +30,8 @@ func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
UserInfo.Id = parsedUserId
UserInfo.Username = claims.(jwt.MapClaims)["username"].(string)
- isAdmin, err := strconv.ParseBool(claims.(jwt.MapClaims)["isAdmin"].(string))
- if err != nil {
- UserInfo.IsAdmin = false
- fmt.Println(global.RedColor + "isAdmin prop corrupted" + global.ResetColor)
- } else {
- UserInfo.IsAdmin = isAdmin
- }
+ isAdmin := claims.(jwt.MapClaims)["isAdmin"].(bool)
+ UserInfo.IsAdmin = isAdmin
return UserInfo, nil
diff --git a/enshi_back/middleware/getters/getIntParam.go b/enshi_back/middleware/getters/getIntParam.go
new file mode 100644
index 0000000..1501981
--- /dev/null
+++ b/enshi_back/middleware/getters/getIntParam.go
@@ -0,0 +1,19 @@
+package getters
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+// Returns -1, error if there is no such param or value invalid
+func GetInt64Param(c *gin.Context, paramName string) (int64, error) {
+ int64ParamValue, err := strconv.ParseInt(strings.Trim(c.Param(paramName), "/"), 10, 64)
+
+ if err != nil {
+ return -1, err
+ }
+
+ return int64ParamValue, nil
+}
diff --git a/enshi_back/middleware/getters/userId.go b/enshi_back/middleware/getters/userId.go
index f4a1319..7afaead 100644
--- a/enshi_back/middleware/getters/userId.go
+++ b/enshi_back/middleware/getters/userId.go
@@ -1,7 +1,7 @@
package getters
import (
- "enshi/middleware"
+ "enshi/global"
"fmt"
"strconv"
@@ -9,7 +9,7 @@ import (
)
func GetUserIdFromContext(c *gin.Context) (int64, error) {
- userId, exists := c.Get(middleware.ContextUserId)
+ userId, exists := c.Get(global.ContextUserId)
if !exists {
return -1, fmt.Errorf("error getting user id")
diff --git a/enshi_back/middleware/keys.go b/enshi_back/middleware/keys.go
deleted file mode 100644
index 3035b5e..0000000
--- a/enshi_back/middleware/keys.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package middleware
-
-var ContextUserId = "id"
-var ContextIsAdmin = "isAdmin"
-var ContextTokenData = "tokenData"
diff --git a/enshi_back/middleware/postVotesMiddleware.go b/enshi_back/middleware/postVotesMiddleware.go
new file mode 100644
index 0000000..0992fab
--- /dev/null
+++ b/enshi_back/middleware/postVotesMiddleware.go
@@ -0,0 +1,38 @@
+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)
+
+ case "POST":
+ c.Set("target", postvotespolicies.CREATE_VOTE)
+
+ case "GET":
+ if a != "post-votes" {
+ c.Set("target", postvotespolicies.READ_VOTE)
+ } else {
+ c.Set("target", "")
+ }
+ }
+
+ isAllowed, errors := postvotespolicies.PostVotePolicies(c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/enshi_back/middleware/postsMiddleware.go b/enshi_back/middleware/postsMiddleware.go
new file mode 100644
index 0000000..824046d
--- /dev/null
+++ b/enshi_back/middleware/postsMiddleware.go
@@ -0,0 +1,41 @@
+package middleware
+
+import (
+ postspolicies "enshi/ABAC/PostsPolicies"
+ "enshi/ABAC/rules"
+ "enshi/middleware/getters"
+
+ "github.com/gin-gonic/gin"
+)
+
+func PostsMiddleware() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ blogId, _ := getters.GetInt64Param(c, "blog-id")
+ postId, _ := getters.GetInt64Param(c, "post-id")
+
+ switch c.Request.Method {
+ case "DELETE":
+ c.Set("target", postspolicies.DELETE_POST)
+ case "PUT":
+ if postId > 0 && blogId > 0 {
+ c.Set("target", postspolicies.UPDATE_POST_BLOG)
+ } else if postId > 0 {
+ c.Set("target", postspolicies.UPDATE_POST)
+ }
+
+ case "POST":
+ c.Set("target", postspolicies.CREATE_POST)
+ case "GET":
+ c.Set("target", postspolicies.GET_POST)
+ }
+
+ isAllowed, errors := postspolicies.PostsPolicies(c)
+
+ if rules.ShouldAbortRequest(c, isAllowed, errors) {
+ c.Abort()
+ return
+ }
+
+ c.Next()
+ }
+}
diff --git a/enshi_back/routes/login.go b/enshi_back/routes/authRoutes/login.go
similarity index 93%
rename from enshi_back/routes/login.go
rename to enshi_back/routes/authRoutes/login.go
index 6d4e3a7..5371dd7 100644
--- a/enshi_back/routes/login.go
+++ b/enshi_back/routes/authRoutes/login.go
@@ -1,4 +1,4 @@
-package routes
+package authRoutes
import (
"context"
@@ -68,6 +68,6 @@ func Login(c *gin.Context) {
c.Header("Authorization", token)
c.SetCookie(cookieName, cookieValue, maxAge, path, domain, secure, httpOnly)
- c.IndentedJSON(http.StatusOK, gin.H{"token": token})
+ c.IndentedJSON(http.StatusOK, gin.H{"token": token, "username": user.Username, "id": user.UserID})
}
diff --git a/enshi_back/routes/registerUser.go b/enshi_back/routes/authRoutes/registerUser.go
similarity index 94%
rename from enshi_back/routes/registerUser.go
rename to enshi_back/routes/authRoutes/registerUser.go
index ffdc7cd..12fc962 100644
--- a/enshi_back/routes/registerUser.go
+++ b/enshi_back/routes/authRoutes/registerUser.go
@@ -1,4 +1,4 @@
-package routes
+package authRoutes
import (
"context"
@@ -10,6 +10,7 @@ import (
"enshi/global"
"enshi/hasher"
"fmt"
+ "net/http"
"time"
"github.com/gin-gonic/gin"
@@ -118,5 +119,5 @@ func RegisterUser(c *gin.Context) {
transaction.Commit(context.Background())
rest_api_stuff.SetCookie(c, cookieParams)
- rest_api_stuff.OkAnswer(c, "User has been created!")
+ c.IndentedJSON(http.StatusOK, gin.H{"status": "All good", "username": userParams.Username, "id": userParams.UserID})
}
diff --git a/enshi_back/routes/blogRoutes/createBlog.go b/enshi_back/routes/blogRoutes/createBlog.go
new file mode 100644
index 0000000..a1f3ede
--- /dev/null
+++ b/enshi_back/routes/blogRoutes/createBlog.go
@@ -0,0 +1,48 @@
+package blogRoutes
+
+import (
+ "context"
+ rest_api_stuff "enshi/REST_API_stuff"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+ "enshi/utils"
+
+ "github.com/gin-gonic/gin"
+)
+
+func CreateBlog(c *gin.Context) {
+ blogParams, err := getters.GetContextPayload[db_repo.CreateBlogByUserIdParams](c)
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ userId, err := getters.GetUserIdFromContext(c)
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ blogId, err := utils.GetUUIDv7AsInt64()
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ blogParams.UserID = userId
+ blogParams.BlogID = blogId
+
+ _, err = db_repo.
+ New(db_connection.Dbx).
+ CreateBlogByUserId(
+ context.Background(),
+ blogParams,
+ )
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "blog has been created")
+}
diff --git a/enshi_back/routes/blogRoutes/deleteBlog.go b/enshi_back/routes/blogRoutes/deleteBlog.go
new file mode 100644
index 0000000..25bc5af
--- /dev/null
+++ b/enshi_back/routes/blogRoutes/deleteBlog.go
@@ -0,0 +1,36 @@
+package blogRoutes
+
+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 DeleteBlog(c *gin.Context) {
+ blogId, err := getters.GetInt64Param(c, "blog-id")
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ transaction, err := db_connection.Dbx.Begin(context.Background())
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+ defer transaction.Rollback(context.Background())
+
+ err = db_repo.New(transaction).
+ DeleteBlogByBlogId(context.Background(), blogId)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ transaction.Commit(context.Background())
+ rest_api_stuff.OkAnswer(c, "blog has been deleted")
+}
diff --git a/enshi_back/routes/blogRoutes/getBlog.go b/enshi_back/routes/blogRoutes/getBlog.go
new file mode 100644
index 0000000..81c20b6
--- /dev/null
+++ b/enshi_back/routes/blogRoutes/getBlog.go
@@ -0,0 +1,29 @@
+package blogRoutes
+
+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 GetBlog(c *gin.Context) {
+ blogId, err := getters.GetInt64Param(c, "blog-id")
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ blogData, err := db_repo.New(db_connection.Dbx).
+ GetBlogByBlogId(context.Background(), blogId)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ c.IndentedJSON(http.StatusOK, blogData)
+}
diff --git a/enshi_back/routes/blogRoutes/getUserBlogs.go b/enshi_back/routes/blogRoutes/getUserBlogs.go
new file mode 100644
index 0000000..184d49e
--- /dev/null
+++ b/enshi_back/routes/blogRoutes/getUserBlogs.go
@@ -0,0 +1,29 @@
+package blogRoutes
+
+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 GetUserBlogs(c *gin.Context) {
+ userId, err := getters.GetUserIdFromContext(c)
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ blogData, err := db_repo.New(db_connection.Dbx).
+ GetBlogsByUserId(context.Background(), userId)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ c.IndentedJSON(http.StatusOK, blogData)
+}
diff --git a/enshi_back/routes/blogRoutes/updateBlog.go b/enshi_back/routes/blogRoutes/updateBlog.go
new file mode 100644
index 0000000..7924db3
--- /dev/null
+++ b/enshi_back/routes/blogRoutes/updateBlog.go
@@ -0,0 +1,46 @@
+package blogRoutes
+
+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 UpdateBlog(c *gin.Context) {
+ newBlogParams, err :=
+ getters.GetContextPayload[db_repo.UpdateBlogInfoByBlogIdParams](c)
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ blogId, err := getters.GetInt64Param(c, "blog-id")
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ newBlogParams.BlogID = blogId
+
+ transaction, err := db_connection.Dbx.Begin(context.Background())
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+ defer transaction.Rollback(context.Background())
+
+ _, err = db_repo.New(transaction).
+ UpdateBlogInfoByBlogId(context.Background(), newBlogParams)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+ transaction.Commit(context.Background())
+
+ rest_api_stuff.OkAnswer(c, "blog has been updated")
+
+}
diff --git a/enshi_back/routes/bookmarksRoutes/createBookmark.go b/enshi_back/routes/bookmarksRoutes/createBookmark.go
new file mode 100644
index 0000000..d12734e
--- /dev/null
+++ b/enshi_back/routes/bookmarksRoutes/createBookmark.go
@@ -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 CreateBookmark(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!")
+}
diff --git a/enshi_back/routes/bookmarksRoutes/deleteBookmark.go b/enshi_back/routes/bookmarksRoutes/deleteBookmark.go
new file mode 100644
index 0000000..b0d9432
--- /dev/null
+++ b/enshi_back/routes/bookmarksRoutes/deleteBookmark.go
@@ -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 DeleteBookmark(c *gin.Context) {
+ var bookmarkParams db_repo.DeleteBookmarkParams
+
+ 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.DeleteBookmark(context.Background(), bookmarkParams); err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "Bookmark has been deleted!")
+}
diff --git a/enshi_back/routes/bookmarksRoutes/getBookmark.go b/enshi_back/routes/bookmarksRoutes/getBookmark.go
new file mode 100644
index 0000000..c2630bc
--- /dev/null
+++ b/enshi_back/routes/bookmarksRoutes/getBookmark.go
@@ -0,0 +1,48 @@
+package bookmarksroutes
+
+import (
+ "context"
+ rest_api_stuff "enshi/REST_API_stuff"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "enshi/middleware/getters"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+)
+
+func GetBookmark(c *gin.Context) {
+ var bookmarkParams db_repo.GetBookmarkTimestampParams
+
+ 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 timestamp, err := query.GetBookmarkTimestamp(context.Background(), bookmarkParams); err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ } else {
+ if timestamp.Valid {
+ c.IndentedJSON(http.StatusOK, gin.H{
+ "isBookmarked": timestamp.Valid,
+ "bookmarkedAt": timestamp.Time,
+ })
+ return
+ } else {
+ c.IndentedJSON(http.StatusOK, gin.H{
+ "isBookmarked": timestamp.Valid,
+ "bookmarkedAt": time.Unix(1<<63-1, 0).UTC(),
+ })
+ }
+ }
+}
diff --git a/enshi_back/routes/changeUserProfile.go b/enshi_back/routes/changeUserProfile.go
deleted file mode 100644
index f4fd3e5..0000000
--- a/enshi_back/routes/changeUserProfile.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package routes
-
-import (
- rest_api_stuff "enshi/REST_API_stuff"
- db_repo "enshi/db/go_queries"
-
- "github.com/gin-gonic/gin"
-)
-
-func ChangeUserProfile(c *gin.Context) {
- var userProfileParams db_repo.UpdateProfileByUserIdParams
-
- if err := c.BindJSON(&userProfileParams); err != nil {
- rest_api_stuff.BadRequestAnswer(c, err)
- }
-
-}
diff --git a/enshi_back/routes/deletePost.go b/enshi_back/routes/deletePost.go
deleted file mode 100644
index 29867e5..0000000
--- a/enshi_back/routes/deletePost.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package routes
-
-import (
- "context"
- rest_api_stuff "enshi/REST_API_stuff"
- db_repo "enshi/db/go_queries"
- "enshi/db_connection"
- "enshi/middleware/getters"
- "fmt"
-
- "github.com/gin-gonic/gin"
-)
-
-func DeletePost(c *gin.Context) {
- var deletePostId struct {
- postId int64
- }
-
- if err := c.BindJSON(&deletePostId); err != nil {
- rest_api_stuff.BadRequestAnswer(c, err)
- return
- }
-
- userClaims, err := getters.GetClaimsFromContext(c)
- if err != nil {
- rest_api_stuff.BadRequestAnswer(c, err)
- return
- }
-
- query := db_repo.New(db_connection.Dbx)
- post, err := query.GetPostsByPostId(context.Background(), deletePostId.postId)
- if err != nil {
- rest_api_stuff.InternalErrorAnswer(c, err)
- return
- }
-
- if post.UserID != userClaims.Id {
- rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you are not the author"))
- return
- }
-
- // TODO: Add block of code, so admin could delete anything
-
- err = query.DeletePostByPostId(context.Background(), deletePostId.postId)
- if err != nil {
- rest_api_stuff.InternalErrorAnswer(c, err)
- return
- }
-
- rest_api_stuff.OkAnswer(c, "post has been deleted")
-}
diff --git a/enshi_back/routes/middlewareSetup.go b/enshi_back/routes/middlewareSetup.go
new file mode 100644
index 0000000..e2be4b2
--- /dev/null
+++ b/enshi_back/routes/middlewareSetup.go
@@ -0,0 +1,159 @@
+package routes
+
+import (
+ globalrules "enshi/ABAC/GlobalRules"
+ "enshi/ABAC/rules"
+ "enshi/middleware"
+)
+
+const (
+ POST_MIDDLEWARE = "POST_MIDDLEWARE"
+ BLOG_MIDDLEWARE = "BLOG_MIDDLEWARE"
+ PROFILE_MIDDLEWARE = "PROFILE_MIDDLEWARE"
+ BOOKMARK_MIDDLEWARE = "BOOKMARK_MIDDLEWARE"
+ POST_BLOG_MIDDLEWARE = "POST_BLOG_MIDDLEWARE"
+ POST_VOTE_MIDDLEWARE = "POST_VOTE_MIDDLEWARE"
+ POST_VOTES_MIDDLEWARE = "POST_VOTES_MIDDLEWARE"
+)
+
+var MiddlewareProvider = middleware.MiddlewareProvider{
+ Policies: make(map[string]middleware.Policy),
+}
+
+var policiesToRegister = map[string]middleware.RulesToCheck{
+ POST_MIDDLEWARE: {
+ middleware.GET: {
+ Rules: make([]rules.RuleFunction, 0),
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.POST: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.PUT: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.DELETE: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsAdminRule,
+ },
+ MustBeCompleted: 2,
+ },
+ },
+
+ BOOKMARK_MIDDLEWARE: {
+ middleware.GET: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.DELETE: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.POST: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ },
+
+ BLOG_MIDDLEWARE: {
+ middleware.GET: {
+ Rules: make([]rules.RuleFunction, 0),
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.POST: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.PUT: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.DELETE: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ globalrules.IsAdminRule,
+ },
+ MustBeCompleted: 2,
+ },
+ },
+
+ POST_VOTE_MIDDLEWARE: {
+ middleware.GET: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.POST: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ middleware.DELETE: {
+ Rules: make([]rules.RuleFunction, 0),
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ },
+
+ POST_VOTES_MIDDLEWARE: {
+ middleware.GET: {
+ Rules: make([]rules.RuleFunction, 0),
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ },
+
+ PROFILE_MIDDLEWARE: {
+ middleware.PUT: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ },
+
+ POST_BLOG_MIDDLEWARE: {
+ middleware.PUT: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+
+ middleware.DELETE: {
+ Rules: []rules.RuleFunction{
+ globalrules.AuthorizedRule,
+ globalrules.IsOwnerOfThePostRule,
+ globalrules.IsOwnerOfTheBlogRule,
+ },
+ MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
+ },
+ },
+}
+
+func InitMiddlewareProvider() {
+ MiddlewareProvider.InitMiddlewareProvider(policiesToRegister)
+}
diff --git a/enshi_back/routes/createPost.go b/enshi_back/routes/postsRoutes/createPost.go
similarity index 97%
rename from enshi_back/routes/createPost.go
rename to enshi_back/routes/postsRoutes/createPost.go
index 65dfc49..2d3b221 100644
--- a/enshi_back/routes/createPost.go
+++ b/enshi_back/routes/postsRoutes/createPost.go
@@ -1,4 +1,4 @@
-package routes
+package postsRoutes
import (
"context"
diff --git a/enshi_back/routes/postsRoutes/deletePost.go b/enshi_back/routes/postsRoutes/deletePost.go
new file mode 100644
index 0000000..83a54f0
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/deletePost.go
@@ -0,0 +1,30 @@
+package postsRoutes
+
+import (
+ "context"
+ rest_api_stuff "enshi/REST_API_stuff"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+)
+
+func DeletePost(c *gin.Context) {
+ postId, err := strconv.ParseInt(c.Param("post-id"), 10, 64)
+
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ query := db_repo.New(db_connection.Dbx)
+
+ err = query.DeletePostByPostId(context.Background(), postId)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "post has been deleted")
+}
diff --git a/enshi_back/routes/postsRoutes/deletePostBlog.go b/enshi_back/routes/postsRoutes/deletePostBlog.go
new file mode 100644
index 0000000..c8265f1
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/deletePostBlog.go
@@ -0,0 +1,35 @@
+package postsRoutes
+
+import (
+ "context"
+ rest_api_stuff "enshi/REST_API_stuff"
+ db_repo "enshi/db/go_queries"
+ "enshi/db_connection"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+func DeletePostBlog(c *gin.Context) {
+ var queryParams db_repo.UpdatePostBlogIdParams
+ postId, err := strconv.ParseInt(c.Param("post-id"), 10, 64)
+
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ queryParams.BlogID = pgtype.Int8{}
+ queryParams.PostID = postId
+
+ query := db_repo.New(db_connection.Dbx)
+
+ err = query.UpdatePostBlogId(context.Background(), queryParams)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "post has been deleted")
+}
diff --git a/enshi_back/routes/postsRoutes/getPost.go b/enshi_back/routes/postsRoutes/getPost.go
new file mode 100644
index 0000000..f05cc2c
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/getPost.go
@@ -0,0 +1,33 @@
+package postsRoutes
+
+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 GetPost(c *gin.Context) {
+ postId, err := getters.GetInt64Param(c, "post-id")
+
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ postData, err :=
+ db_repo.New(db_connection.Dbx).
+ GetPostsByPostId(context.Background(), postId)
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ c.IndentedJSON(http.StatusOK, postData)
+
+}
diff --git a/enshi_back/routes/postsRoutes/getRandomPosts.go b/enshi_back/routes/postsRoutes/getRandomPosts.go
new file mode 100644
index 0000000..f8f2139
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/getRandomPosts.go
@@ -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)
+
+}
diff --git a/enshi_back/routes/postsRoutes/updatePost.go b/enshi_back/routes/postsRoutes/updatePost.go
new file mode 100644
index 0000000..bb26330
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/updatePost.go
@@ -0,0 +1,43 @@
+package postsRoutes
+
+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 UpdatePost(c *gin.Context) {
+ var UpdatedPostParams db_repo.UpdatePostByPostIdParams
+
+ if err := c.BindJSON(&UpdatedPostParams); err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ postId, err := getters.GetInt64Param(c, "post-id")
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ UpdatedPostParams.PostID = postId
+
+ _, err = db_repo.New(
+ db_connection.Dbx,
+ ).UpdatePostByPostId(
+ context.Background(),
+ UpdatedPostParams,
+ )
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "post has been updated")
+}
diff --git a/enshi_back/routes/postsRoutes/updatePostBlog.go b/enshi_back/routes/postsRoutes/updatePostBlog.go
new file mode 100644
index 0000000..0fcb3c8
--- /dev/null
+++ b/enshi_back/routes/postsRoutes/updatePostBlog.go
@@ -0,0 +1,58 @@
+package postsRoutes
+
+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"
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+func UpdatePostBlog(c *gin.Context) {
+ var UpdatedPostParams db_repo.UpdatePostBlogIdParams
+
+ postId, err := getters.GetInt64Param(c, "post-id")
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ blogId, err := getters.GetInt64Param(c, "blog-id")
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ UpdatedPostParams.PostID = postId
+ UpdatedPostParams.BlogID = pgtype.Int8{
+ Valid: true,
+ Int64: blogId,
+ }
+
+ transaction, err := db_connection.Dbx.Begin(context.Background())
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+ defer transaction.Rollback(context.Background())
+
+ err = db_repo.New(
+ transaction,
+ ).UpdatePostBlogId(
+ context.Background(),
+ UpdatedPostParams,
+ )
+
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ transaction.Commit(context.Background())
+ rest_api_stuff.OkAnswer(c, "post has been updated")
+}
diff --git a/enshi_back/routes/routesSetup.go b/enshi_back/routes/routesSetup.go
new file mode 100644
index 0000000..77149d8
--- /dev/null
+++ b/enshi_back/routes/routesSetup.go
@@ -0,0 +1,221 @@
+package routes
+
+import (
+ "enshi/middleware"
+ "enshi/middleware/getters"
+ "enshi/routes/authRoutes"
+ "enshi/routes/blogRoutes"
+ bookmarksroutes "enshi/routes/bookmarksRoutes"
+ "enshi/routes/postsRoutes"
+ "enshi/routes/userProfileRoutes"
+ userroutes "enshi/routes/userRoutes"
+ voteroutes "enshi/routes/voteRoutes"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+func testCookie(c *gin.Context) {
+ cock, _ := c.Cookie("auth_cookie")
+ c.IndentedJSON(http.StatusOK, gin.H{"token": "SLESAR' U STASA " + strings.Split(cock, "_")[0]})
+}
+
+func testAdmin(c *gin.Context) {
+ c.IndentedJSON(http.StatusOK, gin.H{"message": "you are an admin, congrats!"})
+}
+
+func testAuth(c *gin.Context) {
+ userInfo, err := getters.GetClaimsFromContext(c)
+ if err != nil {
+ c.IndentedJSON(http.StatusUnauthorized, gin.H{"message": "you are not logged in"})
+
+ }
+ c.IndentedJSON(
+ http.StatusOK,
+ gin.H{
+ "message": "you are logged in, congrats!",
+ "username": userInfo.Username,
+ "is_admin": userInfo.IsAdmin,
+ "id": userInfo.Id,
+ },
+ )
+}
+
+func SetupRotes(g *gin.Engine) error {
+ InitMiddlewareProvider()
+
+ g.Use(middleware.CORSMiddleware())
+ g.Use(middleware.TargetMiddleware())
+
+ testGroup := g.Group("/test/")
+ testGroup.Use(MiddlewareProvider.GetMiddleware(POST_MIDDLEWARE))
+
+ testGroup.GET(
+ "posts/:post-id",
+ postsRoutes.GetPost,
+ )
+
+ testGroup.GET(
+ "posts/random",
+ postsRoutes.GetRandomPost,
+ )
+
+ testGroup.PUT(
+ "posts/:post-id",
+ postsRoutes.UpdatePost,
+ )
+ testGroup.POST(
+ "posts",
+ postsRoutes.CreatePost,
+ )
+ testGroup.DELETE(
+ "posts/:post-id",
+ postsRoutes.DeletePost,
+ )
+
+ // Free group routes
+ freeGroup := g.Group("/")
+
+ freeGroup.GET("getCookie", testCookie)
+
+ freeGroup.POST(
+ "login",
+ authRoutes.Login,
+ )
+ freeGroup.POST(
+ "users",
+ authRoutes.RegisterUser,
+ )
+
+ postsGroup := g.Group("/")
+ postsGroup.Use(middleware.PostsMiddleware())
+
+ postsGroup.GET(
+ "posts/:post-id",
+ postsRoutes.GetPost,
+ )
+
+ postsGroup.GET(
+ "posts/random",
+ postsRoutes.GetRandomPost,
+ )
+
+ postsGroup.PUT(
+ "posts/:post-id",
+ postsRoutes.UpdatePost,
+ )
+ postsGroup.PUT(
+ "posts/:post-id/blogs/:blog-id",
+ postsRoutes.UpdatePostBlog,
+ )
+ postsGroup.POST(
+ "posts",
+ postsRoutes.CreatePost,
+ )
+ postsGroup.DELETE(
+ "posts/:post-id",
+ postsRoutes.DeletePost,
+ )
+ postsGroup.DELETE(
+ "posts/:post-id/blogs",
+ postsRoutes.DeletePostBlog,
+ )
+
+ blogGroup := g.Group("/")
+ blogGroup.Use(middleware.BlogsMiddleware())
+
+ blogGroup.POST(
+ "blogs",
+ blogRoutes.CreateBlog,
+ )
+
+ blogGroup.PUT(
+ "blogs/:blog-id",
+ blogRoutes.UpdateBlog,
+ )
+
+ blogGroup.DELETE(
+ "blogs/:blog-id",
+ blogRoutes.DeleteBlog,
+ )
+
+ blogGroup.GET(
+ "blogs/:blog-id",
+ blogRoutes.GetBlog,
+ )
+
+ profilesGroup := g.Group("/")
+ profilesGroup.Use(middleware.ProfileMiddleware())
+
+ profilesGroup.PUT(
+ "profiles",
+ userProfileRoutes.UpdateUserProfile,
+ )
+
+ bookmarksGroup := g.Group("/")
+ bookmarksGroup.Use(middleware.BookmarksMiddleware())
+
+ bookmarksGroup.POST(
+ "bookmarks/:post-id",
+ bookmarksroutes.CreateBookmark,
+ )
+
+ bookmarksGroup.DELETE(
+ "bookmarks/:post-id",
+ bookmarksroutes.DeleteBookmark,
+ )
+
+ bookmarksGroup.GET(
+ "bookmarks/:post-id",
+ bookmarksroutes.GetBookmark,
+ )
+
+ postVoteGroup := g.Group("/")
+ postVoteGroup.Use(middleware.PostVotesMiddleware())
+
+ postVoteGroup.POST(
+ "post-votes/:post-id",
+ voteroutes.CreateVote,
+ )
+
+ postVoteGroup.DELETE(
+ "post-votes/:post-id",
+ voteroutes.DeleteVote,
+ )
+
+ postVoteGroup.GET(
+ "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())
+
+ adminGroup.GET("check", testAdmin)
+
+ authGroup := g.Group("/auth/")
+ authGroup.Use(middleware.AuthMiddleware())
+ authGroup.GET("check", testAuth)
+
+ temporal := g.Group("/")
+ temporal.Use(middleware.AuthMiddleware())
+
+ temporal.GET(
+ "/user/blogs",
+ blogRoutes.GetUserBlogs,
+ )
+
+ freeGroup.GET(
+ "/user/:user-id",
+ userroutes.GetUserUsername,
+ )
+
+ return nil
+}
diff --git a/enshi_back/routes/userProfileRoutes/updateUserProfile.go b/enshi_back/routes/userProfileRoutes/updateUserProfile.go
new file mode 100644
index 0000000..2abbd9b
--- /dev/null
+++ b/enshi_back/routes/userProfileRoutes/updateUserProfile.go
@@ -0,0 +1,39 @@
+package userProfileRoutes
+
+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 UpdateUserProfile(c *gin.Context) {
+ newProfile, err :=
+ getters.GetContextPayload[db_repo.UpdateProfileByUserIdParams](c)
+
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+
+ userId, err := getters.GetUserIdFromContext(c)
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ newProfile.UserID = userId
+
+ if _, err := db_repo.New(db_connection.Dbx).UpdateProfileByUserId(
+ context.Background(),
+ newProfile,
+ ); err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "profile was updated")
+}
diff --git a/enshi_back/routes/userRoutes/getUser.go b/enshi_back/routes/userRoutes/getUser.go
new file mode 100644
index 0000000..f12bf57
--- /dev/null
+++ b/enshi_back/routes/userRoutes/getUser.go
@@ -0,0 +1,30 @@
+package userroutes
+
+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 GetUserUsername(c *gin.Context) {
+ userId, err := getters.GetInt64Param(c, "user-id")
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ }
+
+ userInfo, err := db_repo.New(db_connection.Dbx).GetUserUsernameById(
+ context.Background(),
+ userId,
+ )
+ if err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ }
+
+ c.IndentedJSON(http.StatusOK, userInfo)
+
+}
diff --git a/enshi_back/routes/voteRoutes/createVote.go b/enshi_back/routes/voteRoutes/createVote.go
new file mode 100644
index 0000000..037317c
--- /dev/null
+++ b/enshi_back/routes/voteRoutes/createVote.go
@@ -0,0 +1,42 @@
+package voteroutes
+
+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 CreateVote(c *gin.Context) {
+ var postVoteParams db_repo.CreatePostVoteParams
+
+ 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)
+ return
+ }
+ 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)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "Vote has been created!")
+}
diff --git a/enshi_back/routes/voteRoutes/deleteVote.go b/enshi_back/routes/voteRoutes/deleteVote.go
new file mode 100644
index 0000000..baf1fdf
--- /dev/null
+++ b/enshi_back/routes/voteRoutes/deleteVote.go
@@ -0,0 +1,35 @@
+package voteroutes
+
+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 DeleteVote(c *gin.Context) {
+ var postVoteParams db_repo.DeletePostVoteParams
+
+ 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)
+ return
+ }
+ postVoteParams.UserID = userId
+
+ query := db_repo.New(db_connection.Dbx)
+ if err := query.DeletePostVote(context.Background(), postVoteParams); err != nil {
+ rest_api_stuff.InternalErrorAnswer(c, err)
+ return
+ }
+
+ rest_api_stuff.OkAnswer(c, "Vote has been deleted!")
+}
diff --git a/enshi_back/routes/voteRoutes/getVote.go b/enshi_back/routes/voteRoutes/getVote.go
new file mode 100644
index 0000000..a949618
--- /dev/null
+++ b/enshi_back/routes/voteRoutes/getVote.go
@@ -0,0 +1,40 @@
+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 GetVote(c *gin.Context) {
+ var postVoteParams db_repo.GetPostVoteParams
+
+ userId, err := getters.GetUserIdFromContext(c)
+ if err != nil {
+ rest_api_stuff.BadRequestAnswer(c, err)
+ return
+ }
+ 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)
+ return
+ } else {
+ c.IndentedJSON(http.StatusOK, gin.H{
+ "vote": voteData,
+ })
+ }
+}
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)
+ }
+}
diff --git a/enshi_back/utils/cringe.go b/enshi_back/utils/cringe.go
new file mode 100644
index 0000000..a3e0952
--- /dev/null
+++ b/enshi_back/utils/cringe.go
@@ -0,0 +1,49 @@
+package utils
+
+import (
+ "fmt"
+ "reflect"
+)
+
+func ConvertInt64ToStringInStruct(input any) (any, error) {
+ origVal := reflect.ValueOf(input)
+
+ // Ensure input is a struct or pointer to a struct
+ if origVal.Kind() == reflect.Ptr {
+ origVal = origVal.Elem()
+ }
+ if origVal.Kind() != reflect.Struct {
+ return nil, fmt.Errorf("input must be a struct or a pointer to a struct")
+ }
+
+ // Create a new instance of the same type
+ newStruct := reflect.New(origVal.Type()).Elem()
+
+ // Iterate through fields
+ for i := 0; i < origVal.NumField(); i++ {
+ field := origVal.Field(i)
+ newField := newStruct.Field(i)
+
+ if !newField.CanSet() {
+ // Skip unexported fields
+ continue
+ }
+
+ switch field.Kind() {
+ case reflect.Int64:
+ // Convert int64 fields to string if the target is compatible
+ // if newField.Kind() == reflect.Int64 {
+ // fmt.Print("aqwrqfwq", field)
+ // newField.Set(strconv.FormatInt(field.Int(), 10))
+ // }
+ newField.SetString("asd")
+ default:
+ // Copy other fields directly
+ if newField.Type() == field.Type() {
+ newField.Set(field)
+ }
+ }
+ }
+
+ return newStruct.Interface(), nil
+}
diff --git a/enshi_back/utils/routesSetup.go b/enshi_back/utils/routesSetup.go
deleted file mode 100644
index 9bc1057..0000000
--- a/enshi_back/utils/routesSetup.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package utils
-
-import (
- "enshi/middleware"
- "enshi/routes"
- "net/http"
- "strings"
-
- "github.com/gin-gonic/gin"
-)
-
-func testCookie(c *gin.Context) {
- cock, _ := c.Cookie("auth_cookie")
- c.IndentedJSON(http.StatusOK, gin.H{"token": "SLESAR' U STASA " + strings.Split(cock, "_")[0]})
-}
-
-func SetupRotes(g *gin.Engine) error {
- g.Use(middleware.CORSMiddleware())
-
- // Free group routes
- freeGroup := g.Group("/")
-
- freeGroup.GET("getCookie", testCookie)
-
- freeGroup.POST("login", routes.Login)
- freeGroup.POST("registerUser", routes.RegisterUser)
-
- // Auth group routes
- authGroup := g.Group("/")
- authGroup.Use(middleware.AuthMiddleware())
- authGroup.POST("createPost", routes.CreatePost)
- authGroup.POST("deletePost", routes.DeletePost)
-
- adminGroup := authGroup.Group("/admin/")
- adminGroup.Use(middleware.AdminMiddleware())
-
- authGroup.POST("changeUserProfile", routes.ChangeUserProfile)
-
- return nil
-}
diff --git a/enshi_back/utils/uuidGenerator.go b/enshi_back/utils/uuidGenerator.go
new file mode 100644
index 0000000..d3f17b9
--- /dev/null
+++ b/enshi_back/utils/uuidGenerator.go
@@ -0,0 +1,18 @@
+package utils
+
+import (
+ "encoding/binary"
+
+ "github.com/google/uuid"
+)
+
+func GetUUIDv7AsInt64() (int64, error) {
+ uuid, err := uuid.NewV7()
+ if err != nil {
+ return -1, err
+ }
+
+ return -int64(
+ binary.BigEndian.Uint64(uuid[8:]),
+ ), nil
+}
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index 793629c..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,171 +0,0 @@
-{
- "name": "Enshi",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "@tanstack/react-query": "^5.59.0",
- "axios": "^1.7.7"
- }
- },
- "node_modules/@tanstack/query-core": {
- "version": "5.59.0",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.0.tgz",
- "integrity": "sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- }
- },
- "node_modules/@tanstack/react-query": {
- "version": "5.59.0",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.0.tgz",
- "integrity": "sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==",
- "license": "MIT",
- "dependencies": {
- "@tanstack/query-core": "5.59.0"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/tannerlinsley"
- },
- "peerDependencies": {
- "react": "^18 || ^19"
- }
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.7.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
- "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
- "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/react": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
- "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "loose-envify": "^1.1.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- }
- }
-}
diff --git a/package.json b/package.json
deleted file mode 100644
index 82a5d97..0000000
--- a/package.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "dependencies": {
- "@tanstack/react-query": "^5.59.0",
- "axios": "^1.7.7"
- }
-}