diff --git a/compose.yml b/compose.yml index d952794..abe8fd5 100644 --- a/compose.yml +++ b/compose.yml @@ -8,6 +8,8 @@ services: networks: - app-network restart: unless-stopped + volumes: + - /etc/letsencrypt/live/nekiiinkognito.ru:/etc/nginx/ssl:ro enshi_back: build: ./enshi_back diff --git a/enshi/Dockerfile b/enshi/Dockerfile index 119c7a6..9451d94 100644 --- a/enshi/Dockerfile +++ b/enshi/Dockerfile @@ -14,9 +14,5 @@ FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf -# Certificates -COPY ./nginx/fullchain.pem /etc/nginx/ssl/ -COPY ./nginx/privkey.pem /etc/nginx/ssl/ - EXPOSE 80 EXPOSE 443 \ No newline at end of file diff --git a/enshi/nginx/nginx.conf b/enshi/nginx/nginx.conf index a7b2538..f4c8356 100644 --- a/enshi/nginx/nginx.conf +++ b/enshi/nginx/nginx.conf @@ -7,8 +7,8 @@ server { server { listen 443 ssl http2; - # server_name nekiiinkognito.ru www.nekiiinkognito.ru; - server_name localhost; + server_name nekiiinkognito.ru www.nekiiinkognito.ru; + # server_name localhost; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; diff --git a/enshi/package-lock.json b/enshi/package-lock.json index c812543..2109523 100644 --- a/enshi/package-lock.json +++ b/enshi/package-lock.json @@ -1,18 +1,18 @@ { "name": "enshi", - "version": "0.0.0", + "version": "0.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "enshi", - "version": "0.0.0", + "version": "0.1.7", "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-form": "^0.1.0", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-navigation-menu": "^1.2.1", - "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/themes": "^3.1.3", @@ -2234,18 +2234,18 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.1.tgz", - "integrity": "sha512-FnM1fHfCtEZ1JkyfH/1oMiTcFBQvHKl4vD9WnpwkLgtF+UmnXMCad6ECPTaAjcDjam+ndOEJWgHyKDGNteWSHw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.2.tgz", + "integrity": "sha512-EFI1N/S3YxZEW/lJ/H1jY3njlvTd8tBmgKEn4GHi51+aMm94i6NmAJstsm5cu3yJwYqYc93gpCPm21FeAbFk6g==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.0", - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, @@ -2264,6 +2264,27 @@ } } }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", @@ -2280,12 +2301,12 @@ } }, "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", - "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { @@ -2303,6 +2324,47 @@ } } }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", diff --git a/enshi/package.json b/enshi/package.json index b396633..af4b387 100644 --- a/enshi/package.json +++ b/enshi/package.json @@ -1,7 +1,7 @@ { "name": "enshi", "private": true, - "version": "0.1.7", + "version": "0.1.8", "type": "module", "scripts": { "dev": "vite", @@ -15,7 +15,7 @@ "@radix-ui/react-form": "^0.1.0", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-navigation-menu": "^1.2.1", - "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/themes": "^3.1.3", diff --git a/enshi/src/@types/BlogTypes.ts b/enshi/src/@types/BlogTypes.ts new file mode 100644 index 0000000..f638c7e --- /dev/null +++ b/enshi/src/@types/BlogTypes.ts @@ -0,0 +1,8 @@ +export type Blog = { + blog_id: number; + user_id: number; + title: string; + description?: string; + category_id?: number; + created_at: Date; +} \ No newline at end of file diff --git a/enshi/src/App.tsx b/enshi/src/App.tsx index 29bf349..82da0a2 100644 --- a/enshi/src/App.tsx +++ b/enshi/src/App.tsx @@ -13,7 +13,7 @@ const router = createBrowserRouter(routes); export default function App() { return ( - + diff --git a/enshi/src/Components/ArticleViewer/ArticleViewer.tsx b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx index 9cc5024..5a26a57 100644 --- a/enshi/src/Components/ArticleViewer/ArticleViewer.tsx +++ b/enshi/src/Components/ArticleViewer/ArticleViewer.tsx @@ -1,20 +1,11 @@ -import * as Dialog from "@radix-ui/react-dialog"; -import { Cross2Icon } from "@radix-ui/react-icons"; -import { - Box, - Button, - Container, - Flex, - Select, - Separator, - Text, -} from "@radix-ui/themes"; +import { Box, Container, Flex, Separator, Text } from "@radix-ui/themes"; import { useQuery } from "@tanstack/react-query"; import { Interweave } from "interweave"; import { useAtomValue } from "jotai"; import { useParams } from "react-router-dom"; import { axiosLocalhost } from "../../api/axios/axios"; import { userAtom } from "../../AtomStore/AtomStore"; +import AddPostToBlogDialog from "../Dialogs/AddPostToBlogDialog/AddPostToBlogDialog"; import ChangePostButton from "./ChangePostButton/ChangePostButton"; import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader"; import VoteButton, { DOWNVOTE, UPVOTE } from "./VoteButton/VoteButton"; @@ -78,62 +69,7 @@ export default function ArticleViewer(props: TArticleViewer) { /> - - - - - - - - - Add this post to blog - - - - - {`Add "${data.title}" to blog...`} - - - - - - - This - - - This is - updated blog - - - This another - - - - - - - -
- - - -
- - - -
-
-
+ {user ? : null} diff --git a/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx index 18e7dcb..d4404a5 100644 --- a/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx +++ b/enshi/src/Components/ArticleViewer/VoteButton/VoteButton.tsx @@ -1,7 +1,10 @@ import { DoubleArrowDownIcon, DoubleArrowUpIcon } from "@radix-ui/react-icons"; import { IconButton } from "@radix-ui/themes"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; import { axiosLocalhost } from "../../../api/axios/axios"; +import { userAtom } from "../../../AtomStore/AtomStore"; +import useToast from "../../../hooks/useToast"; export const UPVOTE = true; export const DOWNVOTE = false; @@ -14,6 +17,9 @@ type TVoteButton = { export default function VoteButton(props: TVoteButton) { const queryClient = useQueryClient(); + const user = useAtomValue(userAtom); + const createToast = useToast(); + const { data } = useQuery({ queryKey: [props.vote + "voteCheck"], queryFn: async () => { @@ -57,7 +63,13 @@ export default function VoteButton(props: TVoteButton) { voteMutation.mutate()} + onClick={() => { + if(!user) { + createToast({title: "Please log in to vote", description: "You need to be logged in to vote on a post."}); + return; + } + voteMutation.mutate(); + }} > {props.vote ? : } diff --git a/enshi/src/Components/Dialogs/AddPostToBlogDialog/AddPostToBlogDialog.tsx b/enshi/src/Components/Dialogs/AddPostToBlogDialog/AddPostToBlogDialog.tsx new file mode 100644 index 0000000..79b4c5b --- /dev/null +++ b/enshi/src/Components/Dialogs/AddPostToBlogDialog/AddPostToBlogDialog.tsx @@ -0,0 +1,155 @@ +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { Button, Card, Flex, Select, Text, Theme } from "@radix-ui/themes"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { Blog } from "../../../@types/BlogTypes"; +import { axiosLocalhost } from "../../../api/axios/axios"; +import { userAtom } from "../../../AtomStore/AtomStore"; +import useToast from "../../../hooks/useToast"; +import { JSONWithInt64 } from "../../../utils/idnex"; + +export default function AddPostToBlogDialog() { + const navigate = useNavigate(); + + const user = useAtomValue(userAtom); + const [selectedBlog, setSelectedBlog] = useState(""); + + const [isOpen, setIsOpen] = useState(false); + + const params = useParams(); + const createToast = useToast(); + + const { data, refetch } = useQuery({ + queryKey: ["userBlogs"], + queryFn: async () => { + const response = await axiosLocalhost.get("/user/blogs", { + transformResponse: [(data) => data], + }); + + let temp = JSONWithInt64(response.data); + + return temp as Blog[]; + }, + enabled: false, + }); + + const addMutation = useMutation({ + mutationKey: ["addPostToBlog"], + mutationFn: async () => { + const response = await axiosLocalhost.put( + `posts/${params["postId"]}/blogs/${selectedBlog}` + ); + return response.data; + }, + onError: (error) => { + console.error(error); + createToast({ + title: "Error!", + description: "Post have not been added", + }); + }, + onSuccess: () => { + console.log("Post added successfully"); + createToast({ + title: "Success!", + description: "Post added successfully", + }); + }, + onSettled: () => { + console.log("Add mutation is settled"); + }, + }); + + if (!user) { + navigate("/login"); + return null; + } + + return ( + { + setIsOpen(e); + if (e) { + refetch(); + } + }} + > + + + + + + + + + + Add this post to blog + + + + {`Add post to `} + { + setSelectedBlog(e); + }} + > + + + + {data?.map((blog, i) => { + return ( + + {blog.title} + + ); + })} + + + + + + + + {selectedBlog ?
: * Please select a blog to attach the post to.} + +
+ + + +
+ + + + + + + + + + ); +} diff --git a/enshi/src/Components/Dialogs/BlogCreationDialog/BlogCreationDialog.tsx b/enshi/src/Components/Dialogs/BlogCreationDialog/BlogCreationDialog.tsx new file mode 100644 index 0000000..32d1923 --- /dev/null +++ b/enshi/src/Components/Dialogs/BlogCreationDialog/BlogCreationDialog.tsx @@ -0,0 +1,123 @@ +import * as Dialog from "@radix-ui/react-dialog"; +import { Cross2Icon, PlusIcon } from "@radix-ui/react-icons"; +import { Box, Button, Card, Text, Theme } from "@radix-ui/themes"; +import { useMutation } from "@tanstack/react-query"; +import { useState } from "react"; +import { axiosLocalhost } from "../../../api/axios/axios"; +import queryClient from "../../../api/QueryClient/QueryClient"; +import useToast from "../../../hooks/useToast"; + +export default function BlogCreationDialog() { + const createToast = useToast(); + + const [title, setTitle] = useState("My blog"); + const [description, setDescription] = useState(""); + + const addMutation = useMutation({ + onMutate: () => {}, + mutationFn: async () => { + await axiosLocalhost.post("/blogs", { + title, + description, + }); + }, + onSuccess: () => { + createToast({ + title: `Success!`, + description: `Blog created successfully!`, + }); + }, + onError: (_error) => { + createToast({ + title: `Error!`, + description: `Blog creation failed!`, + }); + }, + onSettled: () => { + queryClient.invalidateQueries({ + queryKey: ["userBlogs"], + }); + }, + }); + + return ( + + + + + + + + + + + + + Create blog + + + Create your new blog. + +
+ + { + setTitle(e.target.value); + }} + /> +
+
+ +