Compare commits
No commits in common. "cringe" and "develop" have entirely different histories.
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -3,17 +3,5 @@
|
||||
"downvotes",
|
||||
"godotenv",
|
||||
"upvotes"
|
||||
],
|
||||
"sqltools.connections": [
|
||||
{
|
||||
"previewLimit": 50,
|
||||
"server": "nekiiinkognito.ru",
|
||||
"port": 5432,
|
||||
"askForPassword": true,
|
||||
"driver": "PostgreSQL",
|
||||
"name": "enshi",
|
||||
"database": "postgres",
|
||||
"username": "neki"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
compose.yml
13
compose.yml
@ -22,19 +22,6 @@ services:
|
||||
- DOMAIN=localhost
|
||||
restart: unless-stopped
|
||||
|
||||
jaeger:
|
||||
container_name: jaeger
|
||||
image: jaegertracing/all-in-one:1.41
|
||||
ports:
|
||||
- "6831:6831/udp"
|
||||
- "6832:6832/udp"
|
||||
- "5778:5778"
|
||||
- "16686:16686"
|
||||
- "14268:14268"
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
networks:
|
||||
app-network:
|
||||
driver: bridge
|
||||
@ -1,7 +1,4 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"quoteProps": "consistent"
|
||||
"useTabs": false
|
||||
}
|
||||
|
||||
142
enshi/package-lock.json
generated
142
enshi/package-lock.json
generated
@ -1,14 +1,13 @@
|
||||
{
|
||||
"name": "enshi",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.7",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "enshi",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.7",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-form": "^0.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
@ -19,9 +18,7 @@
|
||||
"@radix-ui/themes": "^3.1.3",
|
||||
"@tanstack/react-query": "^5.55.0",
|
||||
"@tanstack/react-query-devtools": "^5.61.0",
|
||||
"@types/quill": "^2.0.14",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"html-react-parser": "^5.1.16",
|
||||
"i18n": "^0.15.1",
|
||||
"i18next": "^23.14.0",
|
||||
@ -31,11 +28,10 @@
|
||||
"jotai": "^2.9.3",
|
||||
"jotai-immer": "^0.4.1",
|
||||
"primereact": "^10.8.2",
|
||||
"quill": "^2.0.3",
|
||||
"quill": "^2.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-intersection-observer": "^9.15.1",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.26.2"
|
||||
},
|
||||
@ -1298,12 +1294,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-aspect-ratio": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz",
|
||||
"integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.0.tgz",
|
||||
"integrity": "sha512-dP87DM/Y7jFlPgUZTlhx6FF5CEzOiaxp2rBCKlaXlpH5Ip/9Fg5zZ9lDOQ5o/MOfUlf36eak14zoWYpgcgGoOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.2"
|
||||
"@radix-ui/react-primitive": "2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
@ -1320,62 +1316,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-aspect-ratio/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-aspect-ratio/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
|
||||
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.2"
|
||||
},
|
||||
"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-aspect-ratio/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
|
||||
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
|
||||
"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-avatar": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz",
|
||||
@ -2993,29 +2933,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/themes/node_modules/@radix-ui/react-aspect-ratio": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.0.tgz",
|
||||
"integrity": "sha512-dP87DM/Y7jFlPgUZTlhx6FF5CEzOiaxp2rBCKlaXlpH5Ip/9Fg5zZ9lDOQ5o/MOfUlf36eak14zoWYpgcgGoOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.0"
|
||||
},
|
||||
"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/themes/node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
|
||||
@ -3505,13 +3422,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/quill": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-2.0.14.tgz",
|
||||
"integrity": "sha512-zvoXCRnc2Dl8g+7/9VSAmRWPN6oH+MVhTPizmCR+GJCITplZ5VRVzMs4+a/nOE3yzNwEZqylJJrMB07bwbM1/g==",
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parchment": "^1.1.2",
|
||||
"quill-delta": "^5.1.0"
|
||||
"parchment": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/quill/node_modules/parchment": {
|
||||
@ -4297,12 +4213,6 @@
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
@ -6499,9 +6409,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/quill": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^5.0.1",
|
||||
@ -6574,21 +6484,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-intersection-observer": {
|
||||
"version": "9.15.1",
|
||||
"resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.15.1.tgz",
|
||||
"integrity": "sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@ -6616,15 +6511,6 @@
|
||||
"react-dom": "^16 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-quill/node_modules/@types/quill": {
|
||||
"version": "1.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parchment": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/react-quill/node_modules/eventemitter3": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-form": "^0.1.0",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
@ -22,9 +21,7 @@
|
||||
"@radix-ui/themes": "^3.1.3",
|
||||
"@tanstack/react-query": "^5.55.0",
|
||||
"@tanstack/react-query-devtools": "^5.61.0",
|
||||
"@types/quill": "^2.0.14",
|
||||
"axios": "^1.7.7",
|
||||
"dayjs": "^1.11.13",
|
||||
"html-react-parser": "^5.1.16",
|
||||
"i18n": "^0.15.1",
|
||||
"i18next": "^23.14.0",
|
||||
@ -34,11 +31,10 @@
|
||||
"jotai": "^2.9.3",
|
||||
"jotai-immer": "^0.4.1",
|
||||
"primereact": "^10.8.2",
|
||||
"quill": "^2.0.3",
|
||||
"quill": "^2.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-intersection-observer": "^9.15.1",
|
||||
"react-quill": "^2.0.0",
|
||||
"react-router-dom": "^6.26.2"
|
||||
},
|
||||
|
||||
@ -4,25 +4,5 @@ export type GetRandomPostsRow = {
|
||||
user_id: string;
|
||||
title: string;
|
||||
// created_at: Date;
|
||||
};
|
||||
}
|
||||
|
||||
export type TPostData = {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type Post = {
|
||||
blog_id: string | null;
|
||||
created_at: string; // ISO 8601 date string
|
||||
post_id: string;
|
||||
title: string;
|
||||
user_id: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type SelectedPostsResponse = {
|
||||
selected_posts: Post[];
|
||||
has_next_page: boolean;
|
||||
next_page_index: number;
|
||||
prev_page_index: number;
|
||||
};
|
||||
|
||||
@ -3,24 +3,3 @@ export type TUser = {
|
||||
isAdmin: boolean;
|
||||
id?: string | number;
|
||||
}
|
||||
|
||||
export type TInfoUser = {
|
||||
user_id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
createdAt: string;
|
||||
is_admin: boolean;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
export type TProfile = {
|
||||
user_id: number;
|
||||
bio: string;
|
||||
avatar_url: string;
|
||||
website_url: string;
|
||||
};
|
||||
|
||||
export type TGetUserInfoResponse = {
|
||||
user_info: TInfoUser;
|
||||
profile_info: TProfile;
|
||||
};
|
||||
@ -426,16 +426,6 @@
|
||||
content: '';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.ql-toolbar {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ql-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ql-snow.ql-toolbar button,
|
||||
.ql-snow .ql-toolbar button {
|
||||
background: none;
|
||||
|
||||
@ -1,33 +1,23 @@
|
||||
import { Theme } from "@radix-ui/themes";
|
||||
import { Theme, ThemePanel } from "@radix-ui/themes";
|
||||
import "@radix-ui/themes/styles.css";
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import "axios";
|
||||
import dayjs from "dayjs";
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import { useAtomValue } from "jotai";
|
||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||
import queryClient from "./api/QueryClient/QueryClient";
|
||||
import "./App.css";
|
||||
import { themeAtom } from "./AtomStore/AtomStore";
|
||||
import ToastProvider from "./Components/ToastProvider/ToastProvider";
|
||||
import { routes } from "./routes/routes";
|
||||
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const router = createBrowserRouter(routes);
|
||||
|
||||
export default function App() {
|
||||
|
||||
const theme = useAtomValue(themeAtom);
|
||||
|
||||
return (
|
||||
<Theme className="h-fit" accentColor="sky" grayColor="slate" appearance={theme}>
|
||||
<Theme className="h-fit" accentColor="sky" grayColor="slate" appearance="dark">
|
||||
<ToastProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RouterProvider router={router} />
|
||||
{/* <ThemePanel /> */}
|
||||
<ThemePanel />
|
||||
<ReactQueryDevtools/>
|
||||
</QueryClientProvider>
|
||||
</ToastProvider>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { atom } from "jotai";
|
||||
import { atomWithStorage } from "jotai/utils";
|
||||
import { TPostData } from "../@types/PostTypes";
|
||||
import { TUser } from "../@types/UserType";
|
||||
|
||||
export const userAtom = atom<TUser>();
|
||||
@ -8,18 +7,10 @@ export const userAtom = atom<TUser>();
|
||||
export const postCreationAtom = atom<string>();
|
||||
export const postCreationTitleAtom = atom<string>();
|
||||
|
||||
export const themeAtom = atomWithStorage<"light" | "dark">(
|
||||
"theme",
|
||||
"light",
|
||||
{
|
||||
getItem: (key) => localStorage.getItem(key) as any,
|
||||
setItem: (key, value) => localStorage.setItem(key, value as any),
|
||||
removeItem: (key) => localStorage.removeItem(key),
|
||||
},
|
||||
{
|
||||
getOnInit: true,
|
||||
}
|
||||
);
|
||||
type TPostData = {
|
||||
title: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export const storagePostAtom = atomWithStorage<TPostData>(
|
||||
"draft-post",
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { Box, Flex, ScrollArea, 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 { MINUTE } from "../../constants/timeInMills";
|
||||
import AddPostToBlogDialog from "../Dialogs/AddPostToBlogDialog/AddPostToBlogDialog";
|
||||
import ChangePostButton from "./ChangePostButton/ChangePostButton";
|
||||
import SkeletonPostLoader from "./SkeletonLoader/SkeletonLoader";
|
||||
@ -20,7 +19,7 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
let queryParams = useParams();
|
||||
const user = useAtomValue(userAtom);
|
||||
|
||||
const { data: blogData, isPending } = useQuery({
|
||||
const { data, isPending } = useQuery({
|
||||
queryKey: [`post_${queryParams["postId"]}`],
|
||||
queryFn: async () => {
|
||||
const response = await axiosLocalhost.get(
|
||||
@ -29,21 +28,24 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
|
||||
return response.data;
|
||||
},
|
||||
gcTime: 2 * MINUTE,
|
||||
gcTime: 0,
|
||||
refetchOnMount: true,
|
||||
});
|
||||
|
||||
if (isPending) return <SkeletonPostLoader />;
|
||||
|
||||
return (
|
||||
<ScrollArea className="p-0 mx-auto overflow-hidden ql-editor max-w-pc-width">
|
||||
<Flex direction={"column"} className="overflow-hidden">
|
||||
<>
|
||||
<Container size={"3"}>
|
||||
<div className="ql-snow ql-editor">
|
||||
<Container size={"2"} className="mt-4">
|
||||
<Flex direction={"column"}>
|
||||
<Text className="mb-2" as="div" size={"9"}>
|
||||
{blogData.title}
|
||||
{data.title}
|
||||
</Text>
|
||||
<Flex
|
||||
gap={"3"}
|
||||
className="items-center mt-4 mb-2 overflow-hidden align-baseline"
|
||||
className="items-center mt-4 mb-2 align-baseline"
|
||||
>
|
||||
<Flex gap={"1"}>
|
||||
<VoteButton
|
||||
@ -51,7 +53,9 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
|
||||
<VoteCounter postId={queryParams["postId"] || ""} />
|
||||
<VoteCounter
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
|
||||
<VoteButton
|
||||
vote={DOWNVOTE}
|
||||
@ -59,21 +63,20 @@ export default function ArticleViewer(props: TArticleViewer) {
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Box hidden={blogData.user_id != user?.id}>
|
||||
<Box hidden={data.user_id != user?.id}>
|
||||
<ChangePostButton
|
||||
postId={queryParams["postId"] || ""}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{blogData.user_id == user?.id ? <AddPostToBlogDialog /> : null}
|
||||
{user ? <AddPostToBlogDialog /> : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Separator size={"4"} className="my-2" />
|
||||
|
||||
<Text>
|
||||
<Interweave content={blogData.content} />
|
||||
</Text>
|
||||
</ScrollArea>
|
||||
<Separator size={"4"} className="mb-2" />
|
||||
<Interweave content={data.content} />
|
||||
</Container>
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { Delta } from "quill/core";
|
||||
import { forwardRef } from "react";
|
||||
import Sources from "quill";
|
||||
import Quill, { Delta } from "quill/core";
|
||||
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||
import ReactQuill from "react-quill";
|
||||
|
||||
type TEditor = {
|
||||
readOnly?: boolean;
|
||||
@ -28,55 +30,61 @@ const modules = {
|
||||
* @param onChange - function that accepts Delta element
|
||||
*/
|
||||
const Editor = forwardRef((props: TEditor) => {
|
||||
// const editor = useRef(null);
|
||||
// const [quill, setQuill] = useState<Quill | null>(null);
|
||||
// const [value, setValue] = useState(new Delta());
|
||||
const editor = useRef(null);
|
||||
const [quill, setQuill] = useState<Quill | null>(null);
|
||||
const [value, setValue] = useState(new Delta());
|
||||
|
||||
// const [loaded, setLoaded] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (editor.current) {
|
||||
// //@ts-ignore
|
||||
// const temp = editor.current.getEditor() as Quill;
|
||||
// setQuill(temp);
|
||||
// }
|
||||
// return () => {
|
||||
// setQuill(null);
|
||||
// };
|
||||
// }, [editor.current]);
|
||||
useEffect(() => {
|
||||
if (editor.current) {
|
||||
//@ts-ignore
|
||||
const temp = editor.current.getEditor() as Quill;
|
||||
setQuill(temp);
|
||||
}
|
||||
return () => {
|
||||
setQuill(null);
|
||||
};
|
||||
}, [editor.current]);
|
||||
|
||||
// useEffect(() => {
|
||||
// const quill = new Quill(document.createElement("div"));
|
||||
// console.log(`AMOOOGUS`, props.defaultValue);
|
||||
useEffect(() => {
|
||||
const quill = new Quill(document.createElement("div"));
|
||||
const t = quill.clipboard.convert({
|
||||
html: props.defaultValue as string,
|
||||
}) as Delta;
|
||||
|
||||
// const t = quill.clipboard.convert({
|
||||
// html: props.defaultValue as string,
|
||||
// }) as Delta;
|
||||
if (!loaded) {
|
||||
setValue(t);
|
||||
|
||||
// if (!loaded) {
|
||||
// setValue(t);
|
||||
// console.log(t);
|
||||
// }
|
||||
console.log(t);
|
||||
}
|
||||
|
||||
// setLoaded(true);
|
||||
// }, [props.defaultValue]);
|
||||
setLoaded(true);
|
||||
}, [props.defaultValue]);
|
||||
|
||||
// const changeHandler = (
|
||||
// val: string,
|
||||
// _changeDelta: Delta,
|
||||
// _source: Sources,
|
||||
// _editor: ReactQuill.UnprivilegedEditor
|
||||
// ) => {
|
||||
// console.log(val);
|
||||
// console.log(JSON.stringify(quill?.getContents().ops, null, 2));
|
||||
// let fullDelta = quill?.getContents();
|
||||
// if (props.onChange) props.onChange(val || "");
|
||||
// if (loaded) setValue(fullDelta || new Delta());
|
||||
// };
|
||||
const changeHandler = (
|
||||
val: string,
|
||||
_changeDelta: Delta,
|
||||
_source: Sources,
|
||||
_editor: ReactQuill.UnprivilegedEditor
|
||||
) => {
|
||||
console.log(val);
|
||||
console.log(JSON.stringify(quill?.getContents().ops, null, 2));
|
||||
let fullDelta = quill?.getContents();
|
||||
if (props.onChange) props.onChange(val || "");
|
||||
if (loaded) setValue(fullDelta || new Delta());
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-editor h-[400px]">
|
||||
DEPRECATED
|
||||
<div className="text-editor">
|
||||
<ReactQuill
|
||||
value={value}
|
||||
ref={editor}
|
||||
modules={modules}
|
||||
onChange={changeHandler}
|
||||
theme="snow"
|
||||
placeholder="Type your thoughts here..."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,85 +0,0 @@
|
||||
import { Delta, default as Quill, default as Sources } from "quill";
|
||||
import "quill/dist/quill.snow.css"; // make sure to import Quill's CSS
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
type TEditor = {
|
||||
readOnly?: boolean;
|
||||
defaultValue?: string | Delta;
|
||||
onChange?: (html: string) => void;
|
||||
onSelectionChange?: (range: any) => void;
|
||||
};
|
||||
|
||||
const modules = {
|
||||
toolbar: [
|
||||
[{ header: [1, 2, 3, 4, 5, false] }],
|
||||
["bold", "italic", "underline", "strike", "blockquote", "span-wrapper"],
|
||||
[
|
||||
{ list: "ordered" },
|
||||
{ list: "bullet" },
|
||||
{ indent: "-1" },
|
||||
{ indent: "+1" },
|
||||
],
|
||||
["link", "image"],
|
||||
["clean"],
|
||||
[{ align: [] }],
|
||||
],
|
||||
};
|
||||
|
||||
const TrueEditor = (props: TEditor) => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const quillRef = useRef<Quill | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current) {
|
||||
quillRef.current = new Quill(containerRef.current, {
|
||||
modules,
|
||||
theme: "snow",
|
||||
readOnly: props.readOnly || false,
|
||||
placeholder: "Type your thoughts here...",
|
||||
});
|
||||
|
||||
if (props.defaultValue) {
|
||||
if (typeof props.defaultValue === "string") {
|
||||
const delta = quillRef.current.clipboard.convert({ html: props.defaultValue });
|
||||
quillRef.current.setContents(delta, "silent");
|
||||
} else {
|
||||
quillRef.current.setContents(props.defaultValue, "silent");
|
||||
}
|
||||
}
|
||||
|
||||
quillRef.current.on(
|
||||
"text-change",
|
||||
(_delta: Delta, _oldDelta: Delta, _source: Sources) => {
|
||||
if (props.onChange) {
|
||||
const html =
|
||||
containerRef.current?.querySelector(".ql-editor")?.innerHTML ||
|
||||
"";
|
||||
props.onChange(html);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (props.onSelectionChange) {
|
||||
quillRef.current.on("selection-change", (range, _oldRange, _source) => {
|
||||
if(props.onSelectionChange) props.onSelectionChange(range);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (quillRef.current) {
|
||||
quillRef.current.off("text-change");
|
||||
quillRef.current.off("selection-change");
|
||||
quillRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full mx-auto overflow-hidden text-editor flex-grow-1 max-w-pc-width">
|
||||
<div ref={containerRef} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrueEditor;
|
||||
@ -1,18 +1,15 @@
|
||||
import { Flex } from "@radix-ui/themes";
|
||||
import CustomNavigationMenu from "./NavigationMenu/NavigationMenu";
|
||||
import RightButtonBar from "./RightButtonBar/RightButtonBar";
|
||||
import SearchField from "./SearchField/SearchField";
|
||||
|
||||
export default function NavBar() {
|
||||
return (
|
||||
<Flex className="justify-center w-full">
|
||||
<nav className="flex justify-center pt-2 pb-2 ml-4 mr-4 mb-4 mt-2 flex-[1] max-h-fit max-w-pc-width">
|
||||
<nav className="flex justify-center pt-2 pb-2 ml-4 mr-4 flex-[1] max-h-fit">
|
||||
<CustomNavigationMenu />
|
||||
|
||||
<SearchField />
|
||||
|
||||
<RightButtonBar />
|
||||
</nav>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ export default function CustomNavigationMenu() {
|
||||
|
||||
return (
|
||||
<div className="flex-1">
|
||||
<NavigationMenu.Root orientation="horizontal" className="h-full">
|
||||
<NavigationMenu.List className="flex items-center justify-start h-full gap-8 my-auto">
|
||||
<NavigationMenu.Root orientation="horizontal">
|
||||
<NavigationMenu.List className="flex items-center justify-start gap-8">
|
||||
<NavItem text={t("home")} to="/" />
|
||||
|
||||
<NavItem text={t("following")} to="/c" />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Pencil1Icon } from "@radix-ui/react-icons";
|
||||
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";
|
||||
@ -8,8 +8,8 @@ export default function CreatePostButton() {
|
||||
|
||||
return (
|
||||
<Link to={"/create"}>
|
||||
<Button variant="ghost" className="items-center h-full px-[6px] pr-[8px] py-0 my-auto m-0 overflow-hidden">
|
||||
<Pencil1Icon />
|
||||
<Button variant="ghost" className="h-full">
|
||||
<PlusIcon />
|
||||
<Text>{t("createPost")}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import CreatePostButton from "./CreatePostButton/CreatePostButton";
|
||||
import ThemeChangeButton from "./ThemeChangeButton/ThemeChangeButton";
|
||||
import UserButton from "./UserButton/UserButton";
|
||||
|
||||
|
||||
export default function RightButtonBar() {
|
||||
return (
|
||||
<div className='flex flex-row items-center justify-end flex-1 gap-2'>
|
||||
<div className='flex flex-row justify-end flex-1 gap-4'>
|
||||
<CreatePostButton />
|
||||
<ThemeChangeButton />
|
||||
<UserButton />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
|
||||
import { IconButton } from "@radix-ui/themes";
|
||||
import { useAtom } from "jotai";
|
||||
import { themeAtom } from "../../../../AtomStore/AtomStore";
|
||||
|
||||
export default function ThemeChangeButton() {
|
||||
const [theme, setTheme] = useAtom(themeAtom);
|
||||
|
||||
const toggleTheme = () => {
|
||||
if (theme === "light") {
|
||||
setTheme("dark");
|
||||
} else {
|
||||
setTheme("light");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
size={"3"}
|
||||
onClick={toggleTheme}
|
||||
className="mx-0 my-auto rounded-full p-[8px]"
|
||||
variant="ghost"
|
||||
>
|
||||
{theme === "light" ? (
|
||||
<SunIcon className="size-6" />
|
||||
) : (
|
||||
<MoonIcon className="size-6" />
|
||||
)}
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
@ -8,8 +8,8 @@ export default function LoginButton() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Link to={"/login"} className="size-full">
|
||||
<Flex className="items-center justify-start h-full gap-2">
|
||||
<Link to={"/login"}>
|
||||
<Flex className="justify-between gap-2">
|
||||
<Icon>
|
||||
<EnterIcon />
|
||||
</Icon>
|
||||
|
||||
@ -36,7 +36,7 @@ export default function LogoutButton() {
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className="items-center justify-start gap-2 cursor-pointer size-full"
|
||||
className="justify-between gap-2"
|
||||
onClick={() => logoutMutation.mutate()}
|
||||
>
|
||||
<Icon>
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { LaptopIcon, PersonIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
DropdownMenu,
|
||||
Flex,
|
||||
IconButton,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from "@radix-ui/themes";
|
||||
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 { useNavigate } from "react-router-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
import { userAtom } from "../../../../AtomStore/AtomStore";
|
||||
import LoginButton from "./LoginButton/LoginButton";
|
||||
import LogoutButton from "./LogoutButton/LogoutButton";
|
||||
@ -18,51 +15,47 @@ export default function UserButton() {
|
||||
const user = useAtomValue(userAtom);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<DropdownMenu.Root>
|
||||
<Tooltip content={"User menu"} className="w-fit">
|
||||
<DropdownMenu.Trigger>
|
||||
<IconButton
|
||||
size={"3"}
|
||||
className="items-center my-auto rounded-full"
|
||||
variant="ghost"
|
||||
>
|
||||
<PersonIcon className="size-6" />
|
||||
<IconButton className="cursor-pointer">
|
||||
<PersonIcon />
|
||||
</IconButton>
|
||||
</DropdownMenu.Trigger>
|
||||
</Tooltip>
|
||||
|
||||
<DropdownMenu.Content className="w-fit">
|
||||
<DropdownMenu.Item onClick={() => navigate("/profile")}>
|
||||
<div>
|
||||
<Flex className="justify-between w-full gap-2">
|
||||
<DropdownMenu.Item>
|
||||
<Link to={"/user/:user-id/profile"}>
|
||||
<Flex className="justify-between gap-2">
|
||||
<Icon>
|
||||
<PersonIcon />
|
||||
</Icon>
|
||||
|
||||
<Text>{t("profile")}</Text>
|
||||
</Flex>
|
||||
</div>
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item onClick={() => navigate("/user/blogs")}>
|
||||
<div>
|
||||
<DropdownMenu.Item>
|
||||
<Link to={"/user/blogs"}>
|
||||
<Flex className="justify-between gap-2">
|
||||
<Icon>
|
||||
<LaptopIcon />
|
||||
</Icon>
|
||||
<Text>{t("yourBlogs")}</Text>
|
||||
</Flex>
|
||||
</div>
|
||||
</Link>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item color={user ? "red" : "green"}>
|
||||
{user ? <LogoutButton /> : <LoginButton />}
|
||||
{user ? (
|
||||
<LogoutButton />
|
||||
) : (
|
||||
<LoginButton />
|
||||
)}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@ -8,7 +8,7 @@ export default function SearchField() {
|
||||
return (
|
||||
<div className="flex justify-center flex-1">
|
||||
<TextField.Root
|
||||
className="hidden w-2/3 rounded-lg"
|
||||
className="w-2/3 rounded-lg"
|
||||
placeholder={t("search")}
|
||||
>
|
||||
<TextField.Slot>
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import { Box, Button, Text } from "@radix-ui/themes";
|
||||
import { PropsWithChildren, ReactNode } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
type TButtonLink = {
|
||||
label: string;
|
||||
path: string;
|
||||
icon?: ReactNode;
|
||||
} & PropsWithChildren;
|
||||
|
||||
export default function ButtonLink(props: TButtonLink) {
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onClick = () => {
|
||||
navigate(props.path);
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="relative"
|
||||
variant={pathname === props.path ? "solid" : "outline"}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Box className="absolute left-4">{props?.children}</Box>
|
||||
<Text>{props.label}</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import {
|
||||
BoxIcon,
|
||||
FileTextIcon,
|
||||
LockClosedIcon,
|
||||
PersonIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Separator,
|
||||
TabNav,
|
||||
Text
|
||||
} from "@radix-ui/themes";
|
||||
|
||||
import UserCard from "../UserCard/UserCard";
|
||||
import ButtonLink from "./ButtonLink/ButtonLink";
|
||||
import TabLink from "./TabLink/TabLink";
|
||||
|
||||
export default function ProfileNavbar() {
|
||||
return (
|
||||
<>
|
||||
<Flex className="relative flex flex-col sm:hidden">
|
||||
<TabNav.Root size={"2"}>
|
||||
<TabLink label="About" path="/profile" />
|
||||
|
||||
<TabLink label="Security" path="/profile/sec" />
|
||||
|
||||
<TabLink label="Posts" path="/profile/posts" />
|
||||
</TabNav.Root>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"4"}
|
||||
className="absolute min-w-56 shrink collapse sm:relative sm:visible"
|
||||
>
|
||||
<UserCard />
|
||||
|
||||
<Box className="mx-2">
|
||||
<Separator orientation={"horizontal"} size={"4"} />
|
||||
</Box>
|
||||
|
||||
<Flex gap={"2"} direction={"column"}>
|
||||
<ButtonLink label="About" path="/profile">
|
||||
<PersonIcon />
|
||||
</ButtonLink>
|
||||
|
||||
<ButtonLink label="Security" path="/profile/sec">
|
||||
<LockClosedIcon />
|
||||
</ButtonLink>
|
||||
|
||||
<ButtonLink label="Posts" path="/profile/posts">
|
||||
<FileTextIcon />
|
||||
</ButtonLink>
|
||||
|
||||
<Button variant="outline" className="relative">
|
||||
<BoxIcon className="absolute left-4" />
|
||||
<Text className="max-w-[60%]" truncate>
|
||||
Work in progress...
|
||||
</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import { Flex, TabNav, Text } from "@radix-ui/themes";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
type TTabLink = {
|
||||
label: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export default function TabLink(props: TTabLink) {
|
||||
const { pathname } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onClick = () => navigate(props.path);
|
||||
|
||||
return (
|
||||
<TabNav.Link active={pathname === props.path} onClick={onClick}>
|
||||
<Flex className="items-center gap-1">
|
||||
<Text>{props.label}</Text>
|
||||
</Flex>
|
||||
</TabNav.Link>
|
||||
);
|
||||
}
|
||||
28
enshi/src/Components/Tooltip/Tooltip.tsx
Normal file
28
enshi/src/Components/Tooltip/Tooltip.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as RadixTooltip from "@radix-ui/react-tooltip";
|
||||
import { Card, Text, Theme } from "@radix-ui/themes";
|
||||
|
||||
type TTooltipProps = {
|
||||
text: string;
|
||||
} & React.PropsWithChildren;
|
||||
|
||||
export default function Tooltip(props: TTooltipProps) {
|
||||
return (
|
||||
<RadixTooltip.Provider>
|
||||
<RadixTooltip.Root>
|
||||
<RadixTooltip.Trigger>{props.children}</RadixTooltip.Trigger>
|
||||
|
||||
<RadixTooltip.Portal>
|
||||
<RadixTooltip.Content side="top">
|
||||
<RadixTooltip.Content>
|
||||
<Theme panelBackground="translucent">
|
||||
<Card className="p-1 -translate-y-1 w-fit h-fit animate-appearTooltip">
|
||||
<Text>{props.text}</Text>
|
||||
</Card>
|
||||
</Theme>
|
||||
</RadixTooltip.Content>
|
||||
</RadixTooltip.Content>
|
||||
</RadixTooltip.Portal>
|
||||
</RadixTooltip.Root>
|
||||
</RadixTooltip.Provider>
|
||||
);
|
||||
}
|
||||
@ -1,92 +0,0 @@
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
Card,
|
||||
Flex,
|
||||
Separator,
|
||||
Skeleton,
|
||||
Text,
|
||||
} from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { TGetUserInfoResponse } from "../../@types/UserType";
|
||||
import { userAtom } from "../../AtomStore/AtomStore";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import { JSONWithInt64 } from "../../utils/idnex";
|
||||
|
||||
type TUserCard = {
|
||||
username?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
export default function UserCard(props: TUserCard) {
|
||||
const user = useAtomValue(userAtom);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: [`userCard${props.userId || user?.id}`],
|
||||
queryFn: async () => {
|
||||
const response = await axiosLocalhost.get(
|
||||
`/users/info/${props.userId || user?.id?.toString()}`,
|
||||
{
|
||||
transformResponse: [(data) => data],
|
||||
}
|
||||
);
|
||||
|
||||
const parsedResponse = JSONWithInt64(response.data);
|
||||
|
||||
console.log("parsedResponse", parsedResponse);
|
||||
|
||||
return parsedResponse as TGetUserInfoResponse;
|
||||
},
|
||||
});
|
||||
|
||||
const getInitials = (username: string): string => {
|
||||
const result = username
|
||||
.split(" ")
|
||||
.map((word) => word[0].toUpperCase())
|
||||
.join("");
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const getUsername = (): string => {
|
||||
if (!user || props.username) return props.username || "";
|
||||
return user.username;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Flex gap={"2"} direction={"column"}>
|
||||
<Flex direction={"row"} gap={"2"} align={"center"}>
|
||||
<Avatar
|
||||
fallback={<div>{getInitials(getUsername())}</div>}
|
||||
radius="full"
|
||||
loading="lazy"
|
||||
/>
|
||||
<Flex className="flex-col overflow-hidden">
|
||||
{isLoading ? (
|
||||
<Skeleton>
|
||||
<Text truncate>{`Temporal`}</Text>
|
||||
</Skeleton>
|
||||
) : (
|
||||
<Text truncate>
|
||||
{data?.user_info.display_name ||
|
||||
`Non specified`}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Text size={"1"} color={"gray"}>
|
||||
{`@${data?.user_info.username}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Separator size={"4"} />
|
||||
<Flex gap={"2"}>
|
||||
<Badge>test</Badge>
|
||||
<Badge color="amber">user</Badge>
|
||||
<Badge color="red">admin</Badge>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -1,14 +1,12 @@
|
||||
import { HoverCard, Link, Skeleton, Text } from "@radix-ui/themes";
|
||||
import { Skeleton, Text } from "@radix-ui/themes";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
|
||||
type TUserNicknameLink = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
const UserCard = lazy(() => import("../UserCard/UserCard"));
|
||||
|
||||
export default function UserNicknameLink(props: TUserNicknameLink) {
|
||||
const { data, isPending } = useQuery({
|
||||
queryKey: [`userLink${props.userId}`],
|
||||
@ -23,38 +21,13 @@ export default function UserNicknameLink(props: TUserNicknameLink) {
|
||||
if (isPending)
|
||||
return (
|
||||
<Skeleton>
|
||||
<Text
|
||||
size={{
|
||||
sm: "4",
|
||||
md: "5",
|
||||
lg: "6",
|
||||
}}
|
||||
>
|
||||
@Nickname
|
||||
</Text>
|
||||
<Text>@Nickname</Text>
|
||||
</Skeleton>
|
||||
);
|
||||
|
||||
return (
|
||||
<HoverCard.Root>
|
||||
<HoverCard.Trigger>
|
||||
<Link href={`/users/${data}`}>
|
||||
<Text
|
||||
size={{
|
||||
sm: "3",
|
||||
md: "4",
|
||||
lg: "5",
|
||||
}}
|
||||
>
|
||||
@{data}
|
||||
</Text>
|
||||
<Link to={`/users/${data}`}>
|
||||
<Text>@{data}</Text>
|
||||
</Link>
|
||||
</HoverCard.Trigger>
|
||||
<HoverCard.Content className="p-0" maxWidth={'220px'}>
|
||||
<Suspense fallback={<Skeleton />}>
|
||||
<UserCard userId={props.userId} />
|
||||
</Suspense>
|
||||
</HoverCard.Content>
|
||||
</HoverCard.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||
import { userAtom } from "../../../AtomStore/AtomStore";
|
||||
import UseCapsLock from "../../../hooks/useCapsLock";
|
||||
import { JSONWithInt64 } from "../../../utils/idnex";
|
||||
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
||||
|
||||
type TLoginData = {
|
||||
@ -29,18 +28,12 @@ export default function LoginPage() {
|
||||
mutationFn: async (data: TLoginData) => {
|
||||
let response = await axiosLocalhost.post(
|
||||
"/login",
|
||||
JSON.stringify(data),
|
||||
{
|
||||
transformResponse: [data => data]
|
||||
}
|
||||
JSON.stringify(data)
|
||||
);
|
||||
|
||||
const parsedData = JSONWithInt64(response.data);
|
||||
|
||||
setUserAtom({
|
||||
username: parsedData.username,
|
||||
username: response.data.username,
|
||||
isAdmin: false,
|
||||
id: parsedData.id,
|
||||
id: response.data.id,
|
||||
});
|
||||
},
|
||||
|
||||
@ -70,7 +63,7 @@ export default function LoginPage() {
|
||||
return (
|
||||
<Card
|
||||
size={"2"}
|
||||
className="absolute w-[25rem] min-w-[20rem]
|
||||
className="absolute w-1/4
|
||||
left-[50%] top-[50%]
|
||||
translate-x-[-50%] translate-y-[-50%]"
|
||||
>
|
||||
|
||||
@ -2,8 +2,8 @@ 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 TrueEditor from "../../Components/Editor/TrueEditor";
|
||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||
import Editor from "../../../Components/Editor/Editor";
|
||||
import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton";
|
||||
|
||||
export default function PostRedactor() {
|
||||
@ -12,7 +12,7 @@ export default function PostRedactor() {
|
||||
|
||||
const queryParams = useParams();
|
||||
|
||||
const { isLoading } = useQuery({
|
||||
const { isPending } = useQuery({
|
||||
queryKey: ["changePostKey", queryParams.postId],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
@ -31,20 +31,16 @@ export default function PostRedactor() {
|
||||
}
|
||||
},
|
||||
gcTime: 0,
|
||||
refetchOnMount: true,
|
||||
refetchOnMount: true
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className="flex flex-col flex-1 overflow-hidden">
|
||||
<Flex
|
||||
gap={"4"}
|
||||
direction={"column"}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<Container className="">
|
||||
<Box className="flex flex-col flex-1">
|
||||
<Flex gap={"4"} direction={"column"} className="flex-[1]">
|
||||
<Container className="flex-[1]">
|
||||
<input
|
||||
disabled={isLoading}
|
||||
disabled={isPending}
|
||||
placeholder={"Post title"}
|
||||
className="mb-2 border-0 border-b-[1px]
|
||||
outline-none w-full border-b-gray-400
|
||||
@ -56,7 +52,7 @@ export default function PostRedactor() {
|
||||
/>
|
||||
</Container>
|
||||
|
||||
{/* <Container className="overflow-hidden flex-grow-[100]">
|
||||
<Container className="overflow-y-auto flex-grow-[100]">
|
||||
{isPending ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
@ -65,23 +61,13 @@ export default function PostRedactor() {
|
||||
onChange={setContentValue}
|
||||
/>
|
||||
)}
|
||||
</Container> */}
|
||||
|
||||
{isLoading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<TrueEditor
|
||||
defaultValue={contentValue}
|
||||
onChange={setContentValue}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
|
||||
<Box className="flex justify-center flex-[1] mb-4">
|
||||
<SubmitChangesButton
|
||||
contentValue={contentValue}
|
||||
titleValue={titleValue}
|
||||
className="text-2xl rounded-full w-52"
|
||||
/>
|
||||
className="text-2xl rounded-full w-52" />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
@ -3,8 +3,8 @@ 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";
|
||||
import { axiosLocalhost } from "../../../../api/axios/axios";
|
||||
import useToast from "../../../../hooks/useToast";
|
||||
|
||||
type TSubmitChangesButton = {
|
||||
className: string;
|
||||
@ -9,7 +9,6 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||
import { userAtom } from "../../../AtomStore/AtomStore";
|
||||
import UseCapsLock from "../../../hooks/useCapsLock";
|
||||
import { JSONWithInt64 } from "../../../utils/idnex";
|
||||
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
||||
|
||||
type TRegisterData = {
|
||||
@ -32,16 +31,11 @@ export default function RegisterPage() {
|
||||
|
||||
const registerMutation = useMutation({
|
||||
mutationFn: async (data: TRegisterData) => {
|
||||
let response = await axiosLocalhost.post("/users", JSON.stringify(data), {
|
||||
transformResponse: [data => data]
|
||||
});
|
||||
|
||||
const parsedResponse = JSONWithInt64(response.data)
|
||||
|
||||
let response = await axiosLocalhost.post("/users", JSON.stringify(data));
|
||||
setUserAtom({
|
||||
username: parsedResponse.username,
|
||||
username: response.data.username,
|
||||
isAdmin: false,
|
||||
id: parsedResponse.id,
|
||||
id: response.data.id,
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ import { Box, Container, Flex } from "@radix-ui/themes";
|
||||
import { useAtom, useSetAtom } from "jotai";
|
||||
import {
|
||||
postCreationAtom,
|
||||
postCreationTitleAtom,
|
||||
postCreationTitleAtom
|
||||
} from "../../AtomStore/AtomStore";
|
||||
import TrueEditor from "../../Components/Editor/TrueEditor";
|
||||
import Editor from "../../Components/Editor/Editor";
|
||||
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
||||
|
||||
export default function PostCreatorPage() {
|
||||
@ -13,13 +13,10 @@ export default function PostCreatorPage() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className="flex flex-col flex-1 overflow-hidden">
|
||||
<Flex
|
||||
gap={"4"}
|
||||
direction={"column"}
|
||||
className="justify-start overflow-hidden"
|
||||
>
|
||||
<Container>
|
||||
|
||||
<Box className="flex flex-col flex-1">
|
||||
<Flex gap={"4"} direction={"column"} className="flex-[1]">
|
||||
<Container className="flex-[1]">
|
||||
<input
|
||||
placeholder={"Post title"}
|
||||
className="mb-2 border-0 border-b-[1px]
|
||||
@ -32,7 +29,9 @@ export default function PostCreatorPage() {
|
||||
/>
|
||||
</Container>
|
||||
|
||||
<TrueEditor onChange={setContentValue} />
|
||||
<Container className="overflow-y-auto flex-grow-[100]">
|
||||
<Editor onChange={setContentValue} />
|
||||
</Container>
|
||||
|
||||
<Box className="flex justify-center flex-[1] mb-4">
|
||||
<SubmitPostButton className="text-2xl rounded-full w-52" />
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
import { Inset } from "@radix-ui/themes";
|
||||
import { useMemo } from "react";
|
||||
|
||||
type TInsetImageProps = {
|
||||
isHovered: boolean;
|
||||
ref_: React.RefObject<HTMLDivElement>;
|
||||
windowWidth: number;
|
||||
};
|
||||
|
||||
export default function InsetImage(props: TInsetImageProps) {
|
||||
const seed = useMemo(() => {
|
||||
return Math.floor(Math.random() * (1 + Math.random()) * 100000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Inset
|
||||
side={"left"}
|
||||
clip={"padding-box"}
|
||||
className={`max-w-[${
|
||||
props.isHovered ? "100%" : "225px"
|
||||
}] transition-[flex] duration-[250ms]
|
||||
${props.isHovered ? "flex-1" : "flex-[0.5]"}
|
||||
relative overflow-hidden h-72`}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
minWidth: `${
|
||||
Math.min(
|
||||
props.windowWidth,
|
||||
props.ref_.current?.clientWidth || 0
|
||||
) / 2
|
||||
}px`,
|
||||
transform: `${
|
||||
props.isHovered
|
||||
? "translateX(0)"
|
||||
: `translateX(calc(-50% + ${
|
||||
Math.min(
|
||||
props.windowWidth,
|
||||
props.ref_.current?.clientWidth || 0
|
||||
) / 6
|
||||
}px))`
|
||||
}`,
|
||||
}}
|
||||
className={`h-72 transition-all duration-[250ms]`}
|
||||
src={`https://picsum.photos/seed/${seed}/1000/600?grayscale`}
|
||||
alt="Bold typography"
|
||||
/>
|
||||
</Inset>
|
||||
);
|
||||
}
|
||||
@ -1,121 +1,30 @@
|
||||
import { CalendarIcon } from "@radix-ui/react-icons";
|
||||
import { Box, Card, Flex, Heading, Text, Tooltip } from "@radix-ui/themes";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/ru";
|
||||
import { Interweave } from "interweave";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ImageIcon } from "@radix-ui/react-icons";
|
||||
import { Box, Card, Heading } from "@radix-ui/themes";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Post } from "../../../@types/PostTypes";
|
||||
import UserNicknameLink from "../../../Components/UserNicknameLink/UserNicknameLink";
|
||||
import InsetImage from "./InsetImage/InsetImage";
|
||||
import { GetRandomPostsRow } from "../../../@types/PostTypes";
|
||||
|
||||
type TPostCard = {
|
||||
post: GetRandomPostsRow;
|
||||
};
|
||||
|
||||
export default function PostCard(props: Post) {
|
||||
const navigate = useNavigate();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [windowWidth, setWindowWidth] = useState<number>(window.innerWidth);
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
const parsedDate = dayjs(props.created_at)
|
||||
.locale("ru")
|
||||
.format("DD MMMM YYYY");
|
||||
export default function PostCard({ post }: TPostCard) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const clickHandler = () => {
|
||||
navigate(`/posts/${props.post_id.toString()}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const f = () => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
console.log(`Window width: ${window.innerWidth} px`);
|
||||
};
|
||||
|
||||
f();
|
||||
|
||||
window.addEventListener("resize", f);
|
||||
return () => window.removeEventListener("resize", f);
|
||||
}, []);
|
||||
navigate(`/posts/${post.post_id.toString()}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
className="flex w-full cursor-pointer max-h-72"
|
||||
onClick={clickHandler}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<InsetImage
|
||||
isHovered={isHovered}
|
||||
ref_={ref}
|
||||
windowWidth={windowWidth}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
direction={"column"}
|
||||
className="justify-between flex-1 w-full gap-4 px-4 pt-2"
|
||||
>
|
||||
<Heading
|
||||
size={{
|
||||
sm: "4",
|
||||
md: "5",
|
||||
lg: "6",
|
||||
}}
|
||||
className="flex items-center h-fit"
|
||||
>
|
||||
{props.title}
|
||||
</Heading>
|
||||
|
||||
<Flex
|
||||
direction={"column"}
|
||||
justify={"between"}
|
||||
className="h-full overflow-hidden"
|
||||
>
|
||||
<Box className="overflow-y-hidden">
|
||||
<Text
|
||||
size={{
|
||||
sm: "4",
|
||||
md: "5",
|
||||
lg: "6",
|
||||
}}
|
||||
>
|
||||
<Interweave content={props.content} />
|
||||
</Text>
|
||||
<Card className="h-32 mb-4" onClick={clickHandler}>
|
||||
<Box className="flex size-full">
|
||||
<Box>
|
||||
<ImageIcon className="w-full h-full" />
|
||||
</Box>
|
||||
|
||||
<Flex className="gap-2 lg:gap-4">
|
||||
<Tooltip content={`Written at`}>
|
||||
<Flex className="items-center gap-2 h-fit">
|
||||
<CalendarIcon className="size-6" />
|
||||
<Text
|
||||
size={{
|
||||
sm: "3",
|
||||
md: "4",
|
||||
lg: "5",
|
||||
}}
|
||||
weight={"medium"}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{`${parsedDate}`}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
|
||||
<Flex className="items-center gap-2 h-fit">
|
||||
<Text
|
||||
size={{
|
||||
sm: "3",
|
||||
md: "4",
|
||||
lg: "5",
|
||||
}}
|
||||
>
|
||||
Author:{" "}
|
||||
</Text>
|
||||
<UserNicknameLink userId={props.user_id} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box className="px-4 pt-2">
|
||||
<Heading>{post.title}</Heading>
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,80 +1,67 @@
|
||||
import { Box, Flex, ScrollArea, Text } from "@radix-ui/themes";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
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 { useInView } from "react-intersection-observer";
|
||||
import { SelectedPostsResponse } from "../../@types/PostTypes";
|
||||
import { GetRandomPostsRow } from "../../@types/PostTypes";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import PostCard from "./PostCard/PostCard";
|
||||
|
||||
const LIMIT = 7;
|
||||
const LIMIT = 10;
|
||||
|
||||
export default function RandomPostsPage() {
|
||||
const { t } = useTranslation();
|
||||
const {t} = useTranslation()
|
||||
|
||||
const [ref, inView] = useInView();
|
||||
|
||||
const { data, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
||||
queryKey: [`random_post_inf`],
|
||||
queryFn: async ({ pageParam }): Promise<SelectedPostsResponse> => {
|
||||
const { data, refetch } = useQuery({
|
||||
queryKey: ["random_posts_key"],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const response = await axiosLocalhost.get(
|
||||
`/posts/random?limit=${LIMIT}&offset=${pageParam}`
|
||||
`/posts/random?limit=${LIMIT}`
|
||||
);
|
||||
|
||||
return response.data as SelectedPostsResponse;
|
||||
},
|
||||
initialPageParam: 0,
|
||||
getPreviousPageParam: (lastPage) =>
|
||||
lastPage.prev_page_index < 0 ? undefined : lastPage.prev_page_index,
|
||||
getNextPageParam: (lastPage) =>
|
||||
lastPage.next_page_index < 0 ? undefined : lastPage.next_page_index,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (inView) {
|
||||
if (hasNextPage) fetchNextPage();
|
||||
return response.data as GetRandomPostsRow[];
|
||||
} catch (error) {
|
||||
console.log(`Something went wrong`);
|
||||
}
|
||||
}, [inView]);
|
||||
|
||||
return [];
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollArea>
|
||||
<Flex
|
||||
direction={"column"}
|
||||
className="w-full overflow-hidden sm:mx-auto max-w-pc-width "
|
||||
>
|
||||
<Flex direction={"column"} gap={"4"} className="mx-4 xl:mx-0">
|
||||
{data?.pages.map((post, i) => {
|
||||
<Flex direction={"column"} className="mx-auto">
|
||||
<Heading size={"9"} weight={"regular"} className="text-center">
|
||||
{t("discover")}
|
||||
</Heading>
|
||||
|
||||
<Separator size={"4"} className="my-8" />
|
||||
|
||||
<ScrollArea.Root className="w-full h-full overflow-hidden">
|
||||
<ScrollArea.Viewport className="overflow-scroll rounded size-full">
|
||||
{data?.map((post, i) => {
|
||||
return (
|
||||
<Flex
|
||||
direction={"column"}
|
||||
gap={"4"}
|
||||
key={`${i}`}
|
||||
>
|
||||
{post.selected_posts.map((post, j) => {
|
||||
return <PostCard key={j} {...post} />;
|
||||
})}
|
||||
</Flex>
|
||||
<Container size={"3"} key={`post${i}`}>
|
||||
<PostCard post={post} />
|
||||
</Container>
|
||||
);
|
||||
})}
|
||||
|
||||
<Box ref={ref} className="w-full mb-4 text-center">
|
||||
{isFetching ? (
|
||||
<Text>Loading more...</Text>
|
||||
) : hasNextPage ? (
|
||||
<Text
|
||||
className="cursor-pointer"
|
||||
onClick={() => fetchNextPage()}
|
||||
</ScrollArea.Viewport>
|
||||
<ScrollArea.Scrollbar
|
||||
className="z-50 flex touch-none select-none p-0.5 w-2"
|
||||
orientation="vertical"
|
||||
>
|
||||
Load more posts
|
||||
</Text>
|
||||
) : (
|
||||
<Text>No more posts to load</Text>
|
||||
)}
|
||||
</Box>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-slate-200"/>
|
||||
</ScrollArea.Scrollbar>
|
||||
{/* <ScrollArea.Scrollbar
|
||||
className="flex touch-none select-none bg-blackA3 p-0.5 transition-colors duration-[160ms] ease-out hover:bg-blackA5 data-[orientation=horizontal]:h-2.5 data-[orientation=vertical]:w-2.5 data-[orientation=horizontal]:flex-col"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-mauve10 before:absolute before:left-1/2 before:top-1/2 before:size-full before:min-h-[44px] before:min-w-[44px] before:-translate-x-1/2 before:-translate-y-1/2" />
|
||||
</ScrollArea.Scrollbar> */}
|
||||
{/* <ScrollArea.Corner className="bg-blackA5" /> */}
|
||||
</ScrollArea.Root>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -7,6 +7,10 @@ import BlogCreationDialog from "../../Components/Dialogs/BlogCreationDialog/Blog
|
||||
import { JSONWithInt64 } from "../../utils/idnex";
|
||||
import SkeletonBoxes from "./SkeletonBoxes/SkeletonBoxes";
|
||||
|
||||
const TAGS = Array.from({ length: 50 }).map(
|
||||
(_, i, a) => `v1.2.0-beta.${a.length - i}`
|
||||
);
|
||||
|
||||
export default function UserBlogsPage() {
|
||||
const { data, isPending, isFetching } = useQuery({
|
||||
queryKey: ["userBlogs"],
|
||||
@ -15,7 +19,7 @@ export default function UserBlogsPage() {
|
||||
transformResponse: [(data) => data],
|
||||
});
|
||||
|
||||
const temp = JSONWithInt64(response.data);
|
||||
let temp = JSONWithInt64(response.data);
|
||||
|
||||
return temp as any[];
|
||||
},
|
||||
@ -48,14 +52,16 @@ export default function UserBlogsPage() {
|
||||
<ScrollArea.Viewport className="size-full">
|
||||
<Flex direction={"column"} gap={"2"}>
|
||||
{data
|
||||
? data?.map((blog: any, b: number) => {
|
||||
? data?.map((blog: any, b) => {
|
||||
return (
|
||||
<>
|
||||
<BlogBox
|
||||
key={b}
|
||||
title={blog.title}
|
||||
blogId={blog.blog_id}
|
||||
userId={blog.user_id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
export default function UserPostsPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>User Posts Page</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
DataList,
|
||||
Flex,
|
||||
ScrollArea,
|
||||
Separator,
|
||||
Text,
|
||||
TextArea,
|
||||
} from "@radix-ui/themes";
|
||||
|
||||
export default function UserProfilePage() {
|
||||
return (
|
||||
<Flex direction={"column"} className="flex-grow-[8] overflow-hidden">
|
||||
<ScrollArea
|
||||
type="auto"
|
||||
scrollbars="vertical"
|
||||
className="flex-grow-[1] pt-4"
|
||||
>
|
||||
<Text size={"8"}>Base info</Text>
|
||||
|
||||
<Separator className="w-full my-4" />
|
||||
|
||||
<Box className="mb-8">
|
||||
<DataList.Root size={"3"}>
|
||||
<DataList.Item className="items-center">
|
||||
<DataList.Label className="min-w-40">
|
||||
Username
|
||||
</DataList.Label>
|
||||
<DataList.Value>
|
||||
@Definitely_fake_user
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
<DataList.Item>
|
||||
<DataList.Label className="min-w-40">
|
||||
Email
|
||||
</DataList.Label>
|
||||
<DataList.Value>fake@email.com</DataList.Value>
|
||||
</DataList.Item>
|
||||
<DataList.Item>
|
||||
<DataList.Label className="min-w-40">
|
||||
Display name
|
||||
</DataList.Label>
|
||||
<DataList.Value>Isaev</DataList.Value>
|
||||
</DataList.Item>
|
||||
<DataList.Item>
|
||||
<DataList.Label className="min-w-40">
|
||||
Badges
|
||||
</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex
|
||||
gap={"2"}
|
||||
wrap={"wrap"}
|
||||
className="content-evenly"
|
||||
>
|
||||
<Badge size={"3"}>User</Badge>
|
||||
<Badge size={"3"} color="red">
|
||||
Admin
|
||||
</Badge>
|
||||
<Badge size={"3"} color="green">
|
||||
Writer
|
||||
</Badge>
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
</DataList.Root>
|
||||
</Box>
|
||||
|
||||
<Text size={"8"}>
|
||||
Bio
|
||||
</Text>
|
||||
|
||||
<Separator className="w-full my-4" />
|
||||
|
||||
<TextArea resize={"vertical"} placeholder="Add your bio here...">
|
||||
|
||||
</TextArea>
|
||||
|
||||
|
||||
</ScrollArea>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
export default function UserSecurityPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>User Security Page</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
export const loremText = `
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quam phasellus id at vivamus rutrum per justo dapibus elementum dictum litora natoque. Lorem vestibulum arcu platea volutpat eros aenean class odio nec potenti lorem duis. Nunc per tempor accumsan sollicitudin curae praesent egestas venenatis donec aliquam placerat vel. Aliquam fermentum ipsum massa cum facilisi parturient mus egestas dictumst integer platea nec. Ullamcorper nisl sit vivamus nostra litora scelerisque aptent ad cubilia in fusce vehicula. Sem cum cubilia sagittis sed arcu tortor lectus egestas ac sociis orci viverra. Non euismod egestas congue scelerisque at nisl ad morbi mattis felis mus primis. Ultrices tellus aliquet in at sit nam etiam quisque imperdiet taciti vulputate ipsum. Sit vivamus leo at accumsan habitasse dictum mi turpis nisl elit convallis per. Aenean venenatis gravida sociosqu tempor porta senectus aptent turpis purus et bibendum senectus. A neque arcu torquent eleifend urna taciti in dui nec et curae conubia. Condimentum arcu vivamus porttitor per mollis non luctus sociis sociis mollis a dui. Imperdiet aenean euismod aliquet mauris ridiculus consequat varius vulputate tempor lacus mollis odio.
|
||||
|
||||
Imperdiet varius imperdiet neque habitasse eros taciti elit pretium sollicitudin habitasse tortor interdum. Non a dignissim ac cras velit nisi rutrum eu gravida erat class aliquam. Hac hac himenaeos sagittis elit et natoque interdum vehicula lacinia imperdiet adipiscing tortor. Gravida auctor litora ipsum rhoncus felis tellus ridiculus suscipit dignissim praesent blandit eu. Dolor bibendum massa sapien consectetur sodales odio justo blandit sapien posuere magna rhoncus. Nascetur maecenas sociosqu placerat proin varius iaculis viverra laoreet lectus hac habitant ligula. Aenean consequat vitae tempor ligula pharetra condimentum mollis praesent at ipsum quam nam. Dapibus purus orci malesuada penatibus mattis sociosqu consectetur natoque ultricies sagittis pretium nullam. Arcu erat aliquet adipiscing sociis sem ultricies ante mus interdum eu sit arcu. Interdum fusce elementum pharetra parturient tortor eros fermentum dignissim parturient maecenas facilisi mi. Nunc orci conubia lacinia imperdiet dictumst facilisi consectetur nam cum dictumst mollis ornare. Tortor volutpat blandit nunc consequat penatibus eleifend tempus sed quis mauris luctus mattis. Condimentum velit proin taciti vestibulum ante cum ante lorem porta dictumst ornare vehicula. Habitasse fermentum gravida consequat odio volutpat imperdiet nisi velit posuere euismod curae interdum.
|
||||
|
||||
Sem ullamcorper est senectus dapibus ornare congue taciti euismod aenean hac et ullamcorper. Potenti mattis mi ridiculus quis parturient mus id praesent taciti parturient phasellus aenean. Montes parturient cubilia congue montes aenean maecenas neque maecenas iaculis condimentum a metus. Mattis proin nec dictum dictumst consequat lacus phasellus odio porta ac interdum vivamus. Nec euismod ullamcorper erat convallis neque molestie curae metus sociosqu curae congue natoque. Netus porttitor tempus commodo netus interdum faucibus ad nullam malesuada magna tortor auctor. Aptent mus massa per aliquet neque mattis dignissim elit etiam vestibulum justo curae. Urna in vestibulum et vivamus porta duis fringilla curae rhoncus fermentum habitant malesuada. Fermentum odio neque sociis etiam habitant quisque habitasse litora nascetur neque platea porttitor. Varius elit urna venenatis nisl quis consectetur senectus condimentum curae risus a taciti. Ultricies quisque sapien nam consectetur at habitasse faucibus tincidunt eu mollis fusce fringilla. Potenti massa mus iaculis phasellus ac ad consectetur tempor fusce blandit ipsum sociis. Malesuada nam nostra vivamus bibendum bibendum parturient nisi sociosqu tincidunt justo dui dignissim. Interdum habitasse curae etiam parturient convallis faucibus potenti cubilia fusce eu amet quisque.
|
||||
|
||||
Ad ullamcorper sollicitudin phasellus pretium a eleifend vel taciti ipsum ultricies bibendum purus. Ipsum posuere vivamus lacinia felis nulla lacus maecenas aliquam curae nec nostra nostra. Lorem mi nullam dolor sapien quisque nulla eros conubia accumsan netus porttitor vestibulum. Sociosqu quam taciti potenti accumsan nisi cum ridiculus ridiculus accumsan dictum mattis sociis. A sit eu primis eros ultrices ac eleifend volutpat condimentum sociosqu est lacus. Habitant montes venenatis lacinia viverra nascetur vel commodo eleifend pretium vestibulum molestie consequat. Et netus purus risus rhoncus parturient a blandit blandit ante justo vitae nullam. Natoque at torquent eget facilisi taciti scelerisque ridiculus mus vestibulum et enim senectus. Est et aliquet eget vulputate lacinia leo curabitur arcu ornare diam litora taciti. Cum ultrices rhoncus ante nullam odio eleifend neque lobortis rutrum fringilla sed suscipit. Pretium sit vestibulum elementum odio suscipit mollis pellentesque etiam hac sapien lorem himenaeos. Ridiculus ullamcorper varius rutrum porta himenaeos praesent odio habitasse hac netus curae eros. Velit massa sapien platea pulvinar malesuada fermentum varius sapien vehicula mi vel vivamus. Duis turpis ultricies suscipit habitasse curae quisque mus morbi nunc rhoncus malesuada viverra.
|
||||
|
||||
Fusce montes luctus auctor ligula luctus nisi proin per ac nunc imperdiet eleifend. Sem blandit magnis scelerisque etiam sapien cras eleifend vitae justo suspendisse lobortis eu. Eleifend lacus dapibus mi nunc iaculis purus eleifend litora lorem dolor porta luctus. Commodo per nisl senectus dictum cum nostra dolor aenean quam egestas amet venenatis. Feugiat praesent pulvinar leo eleifend turpis netus donec litora adipiscing nascetur aenean ante. Quis mi velit risus venenatis nulla vitae consequat est sapien lorem amet congue. Pharetra venenatis neque nunc quisque a eleifend cum nisi ante netus nostra scelerisque. Etiam felis ullamcorper sodales montes id euismod eget vivamus elit fermentum neque lectus. Massa montes proin pellentesque praesent class felis conubia vivamus bibendum condimentum gravida mattis. Dictumst tempor netus sollicitudin quis aptent vivamus fringilla amet taciti convallis sollicitudin conubia. Dui morbi praesent magnis ligula vivamus proin magnis eget arcu metus lorem egestas. Laoreet dapibus fringilla inceptos pellentesque euismod netus ante urna risus elementum integer nunc. Vivamus inceptos penatibus risus mus consectetur quis lacinia luctus vivamus elementum pharetra consectetur. Ornare donec imperdiet est mi arcu at consequat dignissim eleifend aliquet taciti molestie.
|
||||
|
||||
Sodales nisl magna blandit curae bibendum sociosqu justo phasellus penatibus gravida eu velit. Ornare potenti venenatis tempor integer donec quis sociosqu pulvinar dictum urna interdum euismod. Sodales magna non integer risus purus metus fermentum nascetur interdum ridiculus curabitur blandit. Curabitur senectus id lorem a habitant sed semper accumsan sed euismod sodales volutpat. Bibendum sem quisque lobortis magna nisi magna elit facilisi sed quisque est duis. Imperdiet risus nullam magna potenti commodo eros ridiculus conubia aenean convallis nunc habitant. Rhoncus conubia mus curae maecenas suspendisse suspendisse nisi sed commodo litora tempor bibendum. Dictumst rutrum aliquet consectetur ad laoreet gravida metus montes feugiat quisque nulla elit. Potenti dui erat eleifend himenaeos primis turpis sollicitudin id pretium tortor elementum nec. Pretium nunc quisque morbi senectus himenaeos mollis varius rhoncus venenatis metus magna ipsum. Justo praesent amet dignissim adipiscing primis quam eros duis viverra cum eros rhoncus. Primis pretium erat potenti nisi felis elementum torquent at habitant cubilia nascetur dui. Tortor hac non a adipiscing duis laoreet a maecenas dolor aliquet pretium himenaeos. Netus adipiscing hac class placerat adipiscing accumsan condimentum eros tincidunt potenti conubia integer.
|
||||
|
||||
Magna placerat sem mi nunc pellentesque aliquet habitant aptent posuere faucibus gravida at. Aliquet vestibulum molestie suscipit enim porta sed in dui quam mi lacus suspendisse. Vulputate curae suscipit pretium mauris morbi risus fringilla metus consequat ultricies magnis fringilla. Maecenas lacinia venenatis cras non placerat vitae diam conubia cras enim feugiat sodales. Varius mi parturient fusce tellus nullam consectetur arcu dapibus dictum praesent platea primis. Id natoque sapien non natoque aliquet dapibus pharetra sollicitudin consequat egestas suscipit vestibulum. Congue venenatis ligula iaculis senectus facilisi fermentum ipsum eros mattis gravida venenatis montes. Aliquet placerat interdum senectus magnis montes ullamcorper nisi morbi egestas ultricies vitae lorem. Euismod primis imperdiet elementum sociis tincidunt commodo donec nisi dictumst justo purus convallis. Sapien fusce dolor curae pulvinar velit vestibulum ante vel quis erat duis suscipit. Class dapibus tellus himenaeos magna diam vitae ac cras sociis ultricies dui tellus. Ultricies neque urna orci mus sit parturient netus massa montes dignissim posuere erat. Platea laoreet lectus sagittis fusce duis dui nunc volutpat laoreet primis sapien auctor. Odio ligula himenaeos aliquet nisl class phasellus viverra sapien facilisi cras potenti eleifend.
|
||||
|
||||
Dolor gravida hac dictum consequat phasellus et vulputate non nisl mi sociosqu montes. Risus felis blandit nostra consectetur integer pellentesque quisque varius egestas gravida lobortis natoque. Lacus quisque nullam metus massa hac amet primis phasellus vel odio class ullamcorper. Bibendum orci orci interdum luctus lacinia facilisi senectus bibendum lacus urna tellus mattis. Cras curae erat amet metus consequat mollis cras egestas ligula eget rhoncus ornare. Nulla taciti semper feugiat venenatis tempor sociosqu etiam rhoncus fermentum aliquet torquent ac. Tempus morbi litora dictumst tincidunt ad sem mus leo nostra metus natoque magna. Nullam facilisi lobortis porttitor ornare ad porta morbi donec rhoncus orci cras erat. Lacus eleifend vivamus imperdiet condimentum gravida suscipit ipsum facilisi pretium ridiculus ipsum viverra. Consequat gravida a cras eleifend ultricies leo habitant posuere maecenas magna aliquet cubilia. Semper dolor sit sagittis leo taciti penatibus dictum lacus nunc malesuada taciti eget. Feugiat ad enim posuere dolor risus leo placerat ridiculus condimentum purus porta sapien. Neque dolor fusce morbi class cum erat sociis curae vulputate porttitor viverra sit. Parturient facilisi semper urna ipsum conubia per odio ante vestibulum rhoncus potenti per.
|
||||
|
||||
Quisque suspendisse in lorem felis facilisi elit risus lorem class sapien quam fermentum. Taciti aliquam nunc tempus platea eget litora fusce habitant ullamcorper massa lorem eu. Lacinia hendrerit nulla nisl dignissim nostra massa viverra phasellus magna convallis malesuada habitant. Est nullam eget enim sit montes scelerisque feugiat suscipit id eget sollicitudin tempus. Condimentum mollis faucibus dapibus tincidunt euismod eu interdum vitae mauris dictumst nisi mollis. Inceptos tortor donec tortor convallis fermentum interdum nunc praesent ultricies laoreet et phasellus. Senectus ultricies mi gravida etiam leo nullam viverra pharetra nec praesent pretium eget. Risus eros tempor tempor suspendisse sodales at urna penatibus lorem luctus montes montes. Turpis dapibus eros posuere placerat penatibus sollicitudin congue aptent fusce pretium gravida volutpat. Pellentesque pharetra tempor pretium venenatis suscipit ipsum feugiat dictum morbi sit vivamus sodales. Non quam fusce at suspendisse ultricies tincidunt quis vel etiam sollicitudin cum interdum. Egestas eleifend nulla tellus ullamcorper condimentum sodales commodo tempor lobortis mauris volutpat iaculis. Malesuada velit dignissim fermentum sollicitudin penatibus tincidunt parturient suscipit nisi non etiam molestie. Lectus habitasse odio mollis hac tempor nisi mauris nascetur euismod dignissim natoque urna.
|
||||
|
||||
Consequat rhoncus odio metus lacinia dictumst varius viverra justo leo euismod cras ligula. Mi gravida commodo habitasse purus fermentum etiam luctus natoque sed cras eleifend imperdiet. Sagittis nec orci habitant montes tempus eleifend sociis quis phasellus platea accumsan semper. Hac mattis magnis fringilla nostra montes faucibus bibendum eleifend a netus lobortis urna. Magna laoreet mi luctus condimentum aliquet purus suspendisse donec feugiat penatibus elit nascetur. Dictumst habitasse adipiscing sit nostra eleifend viverra tempus fermentum ornare montes felis netus. Lacinia metus varius vestibulum lectus rutrum pulvinar dictumst velit ullamcorper lectus ante est. Dolor tortor ac massa vel metus ornare habitant volutpat vitae etiam cras in. Duis lorem quisque aliquam dapibus eu quis cras sagittis pulvinar nullam ad luctus. Porta ligula velit erat phasellus elit magnis pharetra est ac enim praesent molestie. Ad inceptos viverra sem adipiscing proin ad non venenatis platea pellentesque inceptos magnis. Pretium bibendum dictumst volutpat cras fringilla dapibus cum lorem tempor bibendum habitasse nulla. Condimentum commodo magna varius ornare torquent adipiscing molestie malesuada non ad parturient turpis. Phasellus magna mauris pellentesque montes scelerisque vehicula hac pharetra iaculis integer leo tempus.
|
||||
`;
|
||||
@ -1,6 +0,0 @@
|
||||
export const SECOND = 1000;
|
||||
export const MINUTE = 60 * SECOND;
|
||||
export const HOUR = 60 * MINUTE;
|
||||
export const DAY = 24 * HOUR;
|
||||
export const WEEK = 7 * DAY;
|
||||
export const MONTH = 30 * DAY;
|
||||
@ -1,5 +1,4 @@
|
||||
@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');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Pochaevsk&display=swap');
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@ -11,12 +10,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--max-content-width: 70rem;
|
||||
}
|
||||
|
||||
.radix-themes {
|
||||
--default-font-family:
|
||||
--default-font-family: "Times New Roman"; ;
|
||||
|
||||
--heading-font-family: "Edu AU VIC WA NT Pre", cursive;
|
||||
/* Your custom font for <Heading> components */
|
||||
--code-font-family:
|
||||
|
||||
@ -5,7 +5,6 @@ import { Outlet } from "react-router-dom";
|
||||
import { axiosLocalhost } from "../../api/axios/axios";
|
||||
import { userAtom } from "../../AtomStore/AtomStore";
|
||||
import NavBar from "../../Components/NavBar/NavBar";
|
||||
import { JSONWithInt64 } from "../../utils/idnex";
|
||||
|
||||
const REFETCH_INTERVAL_IN_MINUTES = 5;
|
||||
const RETRY_INTERVAL_IN_SECONDS = 1;
|
||||
@ -13,6 +12,10 @@ 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);
|
||||
|
||||
@ -20,16 +23,12 @@ export default function MainPage() {
|
||||
queryKey: ["authKey"],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const response = await axiosLocalhost.get("/auth/check", {
|
||||
transformResponse: [data => data]
|
||||
});
|
||||
|
||||
const parsedResponse = JSONWithInt64(response.data)
|
||||
const response = await axiosLocalhost.get("/auth/check");
|
||||
|
||||
setUserData({
|
||||
isAdmin: parsedResponse["is_admin"],
|
||||
username: parsedResponse["username"],
|
||||
id: parsedResponse["id"],
|
||||
isAdmin: response.data["is_admin"],
|
||||
username: response.data["username"],
|
||||
id: response.data["id"],
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
@ -63,11 +62,10 @@ export default function MainPage() {
|
||||
direction={"column"}
|
||||
className="min-h-[100vh] max-h-[100vh] overflow-hidden"
|
||||
>
|
||||
<Box flexGrow={"1"} className="flex-[1]">
|
||||
<NavBar />
|
||||
<Box
|
||||
flexGrow={"1"}
|
||||
className="flex flex-col overflow-hidden"
|
||||
>
|
||||
</Box>
|
||||
<Box flexGrow={"100"} className="flex overflow-hidden flex-">
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import { Box, Flex, Separator } from "@radix-ui/themes";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import ProfileNavbar from "../../Components/ProfileNavbar/ProfileNavbar";
|
||||
|
||||
export default function ProfilePage() {
|
||||
return (
|
||||
<Flex
|
||||
className={`
|
||||
relative flex-col flex-1 gap-0 mx-4
|
||||
sm:flex-row sm:gap-4 sm:mx-4
|
||||
md:w-full md:max-w-pc-width md:mx-auto
|
||||
overflow-hidden
|
||||
`}
|
||||
>
|
||||
<Box className="">
|
||||
<ProfileNavbar />
|
||||
</Box>
|
||||
|
||||
<Box className="my-2 collapse sm:visible">
|
||||
<Separator orientation="vertical" size={"4"} />
|
||||
</Box>
|
||||
|
||||
<Outlet />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@ -7,18 +7,14 @@ import {
|
||||
} from "react-router-dom";
|
||||
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
||||
import MainPage from "../layout/MainPage/MainPage";
|
||||
import ProfilePage from "../layout/ProfilePage/ProfilePage";
|
||||
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 PostRedactor from "../Pages/PostRedactor/PostRedactor";
|
||||
import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
|
||||
import UserBlogsPage from "../Pages/UserBlogsPage/UserBlogsPage";
|
||||
import UserPostsPage from "../Pages/UserPostsPage/UserPostsPage";
|
||||
import UserProfilePage from "../Pages/UserProfilePage/UserProfilePage";
|
||||
import UserSecurityPage from "../Pages/UserSecurityPage/UserSecurityPage";
|
||||
|
||||
function ErrorBoundary() {
|
||||
let error = useRouteError();
|
||||
@ -50,21 +46,6 @@ export const routes = createRoutesFromElements(
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path="profile"
|
||||
element={
|
||||
<AuthPageWrapper>
|
||||
<ProfilePage />
|
||||
</AuthPageWrapper>
|
||||
}
|
||||
>
|
||||
<Route index element={<UserProfilePage />} />
|
||||
|
||||
<Route path="posts" element={<UserPostsPage />} />
|
||||
|
||||
<Route path='sec' element={<UserSecurityPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path="blogs/:blogId" element={<BlogPage />} />
|
||||
|
||||
<Route path="user" element={<Outlet />}>
|
||||
|
||||
@ -7,9 +7,6 @@ export default {
|
||||
"primary-color": "var(--primary-color)",
|
||||
"secondary-color": "var(--secondary-color)",
|
||||
},
|
||||
maxWidth: {
|
||||
"pc-width": "var(--max-content-width)",
|
||||
},
|
||||
fontFamily: {
|
||||
'times': "Times New Roman"
|
||||
},
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
package globalrules
|
||||
|
||||
import (
|
||||
"context"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"enshi/middleware/getters"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func IsOwnerOfTheCommentRule(c *gin.Context) (bool, []error) {
|
||||
commentId, err := getters.GetInt64Param(c, "comment-id")
|
||||
if err != nil {
|
||||
return false, []error{err}
|
||||
}
|
||||
|
||||
contextUserId, err := getters.GetUserIdFromContext(c)
|
||||
if err != nil {
|
||||
return false, []error{err}
|
||||
}
|
||||
|
||||
comment, err := db_repo.New(db_connection.Dbx).GetCommentById(context.Background(), commentId)
|
||||
if err != nil {
|
||||
return false, []error{err}
|
||||
}
|
||||
return contextUserId == comment.UserID, nil
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
NAME=$1
|
||||
|
||||
NEXT_INDEX=$(ls migrations/*.up.sql | wc -l)
|
||||
NEXT_INDEX=$((NEXT_INDEX + 1))
|
||||
|
||||
UP_FILE="migrations/${NEXT_INDEX}_$NAME.up.sql"
|
||||
DOWN_FILE="migrations/${NEXT_INDEX}_$NAME.down.sql"
|
||||
|
||||
touch "$UP_FILE" "$DOWN_FILE"
|
||||
|
||||
echo "Created migration files:"
|
||||
echo " $UP_FILE"
|
||||
echo " $DOWN_FILE"
|
||||
@ -1,243 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// source: badge_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addBadgeToUser = `-- name: AddBadgeToUser :one
|
||||
INSERT INTO public.users_badges (user_id, badge_id)
|
||||
VALUES
|
||||
(
|
||||
$1,
|
||||
$2
|
||||
)
|
||||
RETURNING user_id, badge_id
|
||||
`
|
||||
|
||||
type AddBadgeToUserParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
BadgeID uuid.UUID `json:"badge_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) AddBadgeToUser(ctx context.Context, arg AddBadgeToUserParams) (UsersBadge, error) {
|
||||
row := q.db.QueryRow(ctx, addBadgeToUser, arg.UserID, arg.BadgeID)
|
||||
var i UsersBadge
|
||||
err := row.Scan(&i.UserID, &i.BadgeID)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteBadge = `-- name: DeleteBadge :exec
|
||||
DELETE FROM public.badges
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteBadge(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteBadge, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllBadges = `-- name: GetAllBadges :many
|
||||
SELECT id, name, description, color
|
||||
FROM public.badges
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllBadges(ctx context.Context) ([]Badge, error) {
|
||||
rows, err := q.db.Query(ctx, getAllBadges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Badge
|
||||
for rows.Next() {
|
||||
var i Badge
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Color,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getBadgeByID = `-- name: GetBadgeByID :one
|
||||
SELECT id, name, description, color
|
||||
FROM public.badges
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetBadgeByID(ctx context.Context, id uuid.UUID) (Badge, error) {
|
||||
row := q.db.QueryRow(ctx, getBadgeByID, id)
|
||||
var i Badge
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Color,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserBadges = `-- name: GetUserBadges :many
|
||||
SELECT b.id, b.name, b.description, b.color
|
||||
FROM public.badges b
|
||||
JOIN public.users_badges ub ON b.id = ub.badge_id
|
||||
WHERE ub.user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserBadges(ctx context.Context, userID int64) ([]Badge, error) {
|
||||
rows, err := q.db.Query(ctx, getUserBadges, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Badge
|
||||
for rows.Next() {
|
||||
var i Badge
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Color,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getUsersWithBadge = `-- name: GetUsersWithBadge :many
|
||||
SELECT u.user_id, u.username, u.email, u.password, u.created_at, u.is_admin, u.display_name
|
||||
FROM public.users u
|
||||
JOIN public.users_badges ub ON u.user_id = ub.user_id
|
||||
WHERE ub.badge_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUsersWithBadge(ctx context.Context, badgeID uuid.UUID) ([]User, error) {
|
||||
rows, err := q.db.Query(ctx, getUsersWithBadge, badgeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []User
|
||||
for rows.Next() {
|
||||
var i User
|
||||
if err := rows.Scan(
|
||||
&i.UserID,
|
||||
&i.Username,
|
||||
&i.Email,
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const insertBadge = `-- name: InsertBadge :one
|
||||
INSERT INTO public.badges (id, name, description, color)
|
||||
VALUES
|
||||
(
|
||||
uuid_generate_v4(),
|
||||
$1,
|
||||
$2,
|
||||
$3
|
||||
)
|
||||
RETURNING id, name, description, color
|
||||
`
|
||||
|
||||
type InsertBadgeParams struct {
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Color pgtype.Text `json:"color"`
|
||||
}
|
||||
|
||||
func (q *Queries) InsertBadge(ctx context.Context, arg InsertBadgeParams) (Badge, error) {
|
||||
row := q.db.QueryRow(ctx, insertBadge, arg.Name, arg.Description, arg.Color)
|
||||
var i Badge
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Color,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const removeBadgeFromUser = `-- name: RemoveBadgeFromUser :exec
|
||||
DELETE FROM public.users_badges
|
||||
WHERE
|
||||
user_id = $1
|
||||
AND badge_id = $2
|
||||
`
|
||||
|
||||
type RemoveBadgeFromUserParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
BadgeID uuid.UUID `json:"badge_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) RemoveBadgeFromUser(ctx context.Context, arg RemoveBadgeFromUserParams) error {
|
||||
_, err := q.db.Exec(ctx, removeBadgeFromUser, arg.UserID, arg.BadgeID)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateBadge = `-- name: UpdateBadge :one
|
||||
UPDATE public.badges
|
||||
SET
|
||||
name = $2,
|
||||
description = $3,
|
||||
color = $4
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING id, name, description, color
|
||||
`
|
||||
|
||||
type UpdateBadgeParams struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Color pgtype.Text `json:"color"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateBadge(ctx context.Context, arg UpdateBadgeParams) (Badge, error) {
|
||||
row := q.db.QueryRow(ctx, updateBadge,
|
||||
arg.ID,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.Color,
|
||||
)
|
||||
var i Badge
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.Color,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: blogs_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: bookmarks_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: categories_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: comments_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const createComment = `-- name: CreateComment :one
|
||||
@ -18,9 +20,9 @@ RETURNING comment_id, post_id, user_id, content, created_at
|
||||
|
||||
type CreateCommentParams struct {
|
||||
CommentID int64 `json:"comment_id"`
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
PostID pgtype.Int8 `json:"post_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateComment(ctx context.Context, arg CreateCommentParams) (Comment, error) {
|
||||
@ -51,25 +53,6 @@ func (q *Queries) DeleteComment(ctx context.Context, commentID int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const getCommentById = `-- name: GetCommentById :one
|
||||
SELECT comment_id, post_id, user_id, "content", created_at
|
||||
FROM public."comments"
|
||||
WHERE comment_id=$1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCommentById(ctx context.Context, commentID int64) (Comment, error) {
|
||||
row := q.db.QueryRow(ctx, getCommentById, commentID)
|
||||
var i Comment
|
||||
err := row.Scan(
|
||||
&i.CommentID,
|
||||
&i.PostID,
|
||||
&i.UserID,
|
||||
&i.Content,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getCommentByUserId = `-- name: GetCommentByUserId :one
|
||||
SELECT comment_id, post_id, user_id, "content", created_at
|
||||
FROM public."comments"
|
||||
@ -77,8 +60,8 @@ where public."comments".user_id = $1 and public."comments".post_id = $2
|
||||
`
|
||||
|
||||
type GetCommentByUserIdParams struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
PostID pgtype.Int8 `json:"post_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCommentByUserId(ctx context.Context, arg GetCommentByUserIdParams) (Comment, error) {
|
||||
@ -103,12 +86,12 @@ LIMIT 10 offset ($2 * 10)
|
||||
`
|
||||
|
||||
type GetCommentsForPostAscParams struct {
|
||||
PostID int64 `json:"post_id"`
|
||||
Offset interface{} `json:"offset"`
|
||||
PostID pgtype.Int8 `json:"post_id"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetCommentsForPostAsc(ctx context.Context, arg GetCommentsForPostAscParams) ([]Comment, error) {
|
||||
rows, err := q.db.Query(ctx, getCommentsForPostAsc, arg.PostID, arg.Offset)
|
||||
rows, err := q.db.Query(ctx, getCommentsForPostAsc, arg.PostID, arg.Column2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -142,7 +125,7 @@ LIMIT 10 offset ($2 * 10)
|
||||
`
|
||||
|
||||
type GetCommentsForPostDescParams struct {
|
||||
PostID int64 `json:"post_id"`
|
||||
PostID pgtype.Int8 `json:"post_id"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
}
|
||||
|
||||
@ -181,7 +164,7 @@ RETURNING comment_id, post_id, user_id, content, created_at
|
||||
|
||||
type UpdateCommentByCommentIdParams struct {
|
||||
CommentID int64 `json:"comment_id"`
|
||||
Content string `json:"content"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateCommentByCommentId(ctx context.Context, arg UpdateCommentByCommentIdParams) (Comment, error) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package db_repo
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: favorites_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: likes_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,21 +1,13 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
|
||||
package db_repo
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Badge struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Color pgtype.Text `json:"color"`
|
||||
}
|
||||
|
||||
type Blog struct {
|
||||
BlogID int64 `json:"blog_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
@ -38,9 +30,9 @@ type Category struct {
|
||||
|
||||
type Comment struct {
|
||||
CommentID int64 `json:"comment_id"`
|
||||
PostID int64 `json:"post_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
Content string `json:"content"`
|
||||
PostID pgtype.Int8 `json:"post_id"`
|
||||
UserID pgtype.Int8 `json:"user_id"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
}
|
||||
|
||||
@ -83,7 +75,6 @@ type Profile struct {
|
||||
Bio pgtype.Text `json:"bio"`
|
||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||
WebsiteUrl pgtype.Text `json:"website_url"`
|
||||
EmailVerified pgtype.Bool `json:"email_verified"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
@ -98,10 +89,4 @@ type User struct {
|
||||
Password string `json:"password" validate:"required"`
|
||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
DisplayName pgtype.Text `json:"display_name"`
|
||||
}
|
||||
|
||||
type UsersBadge struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
BadgeID uuid.UUID `json:"badge_id"`
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: multi_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: post_tags_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: post_votes_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: posts_queries.sql
|
||||
|
||||
package db_repo
|
||||
@ -146,70 +146,45 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getRandomPosts = `-- name: GetRandomPosts :one
|
||||
WITH all_posts AS (
|
||||
SELECT
|
||||
COUNT(*) AS post_count
|
||||
FROM
|
||||
public.posts
|
||||
),
|
||||
filtered_posts AS (
|
||||
SELECT
|
||||
ARRAY(
|
||||
SELECT
|
||||
json_build_object(
|
||||
'post_id', post_id::text, 'blog_id', blog_id::text,
|
||||
'user_id', user_id::text, 'title', title,
|
||||
'created_at', created_at, 'content', SUBSTRING(content FROM 1 FOR 300)
|
||||
)
|
||||
FROM
|
||||
public.posts
|
||||
ORDER BY
|
||||
created_at DESC
|
||||
LIMIT $1 OFFSET $3
|
||||
) as selected_posts
|
||||
)
|
||||
SELECT
|
||||
fp.selected_posts,
|
||||
(ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page,
|
||||
case
|
||||
when (ap.post_count - ($2 + 1) * $1)::int > 0
|
||||
then $2 + 1
|
||||
else -1
|
||||
end as next_page_index,
|
||||
case
|
||||
when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count
|
||||
then $2 - 1
|
||||
else -1
|
||||
end as prev_page_index
|
||||
FROM
|
||||
filtered_posts fp,
|
||||
all_posts ap
|
||||
const getRandomPosts = `-- name: GetRandomPosts :many
|
||||
SELECT post_id, blog_id, user_id, title, created_at
|
||||
FROM public.posts
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1
|
||||
`
|
||||
|
||||
type GetRandomPostsParams struct {
|
||||
Column1 interface{} `json:"column_1"`
|
||||
Column2 interface{} `json:"column_2"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type GetRandomPostsRow struct {
|
||||
SelectedPosts interface{} `json:"selected_posts"`
|
||||
HasNextPage bool `json:"has_next_page"`
|
||||
NextPageIndex int32 `json:"next_page_index"`
|
||||
PrevPageIndex int32 `json:"prev_page_index"`
|
||||
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, arg GetRandomPostsParams) (GetRandomPostsRow, error) {
|
||||
row := q.db.QueryRow(ctx, getRandomPosts, arg.Column1, arg.Column2, arg.Offset)
|
||||
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
|
||||
err := row.Scan(
|
||||
&i.SelectedPosts,
|
||||
&i.HasNextPage,
|
||||
&i.NextPageIndex,
|
||||
&i.PrevPageIndex,
|
||||
)
|
||||
return i, err
|
||||
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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: profiles_queries.sql
|
||||
|
||||
package db_repo
|
||||
@ -15,7 +15,7 @@ const clearProfileByUserId = `-- name: ClearProfileByUserId :one
|
||||
UPDATE public.profiles
|
||||
SET bio='', avatar_url='', website_url=''
|
||||
WHERE user_id=$1
|
||||
RETURNING user_id, bio, avatar_url, website_url, email_verified
|
||||
RETURNING user_id, bio, avatar_url, website_url
|
||||
`
|
||||
|
||||
func (q *Queries) ClearProfileByUserId(ctx context.Context, userID int64) (Profile, error) {
|
||||
@ -26,7 +26,6 @@ func (q *Queries) ClearProfileByUserId(ctx context.Context, userID int64) (Profi
|
||||
&i.Bio,
|
||||
&i.AvatarUrl,
|
||||
&i.WebsiteUrl,
|
||||
&i.EmailVerified,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -35,7 +34,7 @@ const createProfileForUser = `-- name: CreateProfileForUser :one
|
||||
INSERT INTO public.profiles
|
||||
(user_id, bio, avatar_url, website_url)
|
||||
VALUES($1, '', '', '')
|
||||
RETURNING user_id, bio, avatar_url, website_url, email_verified
|
||||
RETURNING user_id, bio, avatar_url, website_url
|
||||
`
|
||||
|
||||
func (q *Queries) CreateProfileForUser(ctx context.Context, userID int64) (Profile, error) {
|
||||
@ -46,7 +45,6 @@ func (q *Queries) CreateProfileForUser(ctx context.Context, userID int64) (Profi
|
||||
&i.Bio,
|
||||
&i.AvatarUrl,
|
||||
&i.WebsiteUrl,
|
||||
&i.EmailVerified,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -62,7 +60,7 @@ func (q *Queries) DeleteProfileByUserId(ctx context.Context, userID int64) error
|
||||
}
|
||||
|
||||
const getProfileByUserId = `-- name: GetProfileByUserId :one
|
||||
SELECT user_id, bio, avatar_url, website_url, email_verified FROM public.profiles WHERE user_id = $1
|
||||
SELECT user_id, bio, avatar_url, website_url FROM public.profiles WHERE user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetProfileByUserId(ctx context.Context, userID int64) (Profile, error) {
|
||||
@ -73,7 +71,6 @@ func (q *Queries) GetProfileByUserId(ctx context.Context, userID int64) (Profile
|
||||
&i.Bio,
|
||||
&i.AvatarUrl,
|
||||
&i.WebsiteUrl,
|
||||
&i.EmailVerified,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -82,7 +79,7 @@ const updateProfileByUserId = `-- name: UpdateProfileByUserId :one
|
||||
UPDATE public.profiles
|
||||
SET bio=$2, avatar_url=$3, website_url=$4
|
||||
WHERE user_id=$1
|
||||
RETURNING user_id, bio, avatar_url, website_url, email_verified
|
||||
RETURNING user_id, bio, avatar_url, website_url
|
||||
`
|
||||
|
||||
type UpdateProfileByUserIdParams struct {
|
||||
@ -105,7 +102,6 @@ func (q *Queries) UpdateProfileByUserId(ctx context.Context, arg UpdateProfileBy
|
||||
&i.Bio,
|
||||
&i.AvatarUrl,
|
||||
&i.WebsiteUrl,
|
||||
&i.EmailVerified,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: tags_queries.sql
|
||||
|
||||
package db_repo
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.27.0
|
||||
// source: users_queries.sql
|
||||
|
||||
package db_repo
|
||||
@ -13,7 +13,7 @@ const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO public.users
|
||||
(user_id, username, email, "password", created_at, is_admin)
|
||||
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP, false)
|
||||
RETURNING user_id, username, email, password, created_at, is_admin, display_name
|
||||
RETURNING user_id, username, email, password, created_at, is_admin
|
||||
`
|
||||
|
||||
type CreateUserParams struct {
|
||||
@ -38,7 +38,6 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -64,7 +63,7 @@ func (q *Queries) DeleteUserByUsername(ctx context.Context, username string) err
|
||||
}
|
||||
|
||||
const getAllUsers = `-- name: GetAllUsers :many
|
||||
SELECT user_id, username, email, password, created_at, is_admin, display_name FROM users
|
||||
SELECT user_id, username, email, password, created_at, is_admin FROM users
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
||||
@ -83,7 +82,6 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -96,7 +94,7 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
||||
}
|
||||
|
||||
const getUserByEmailOrNickname = `-- name: GetUserByEmailOrNickname :one
|
||||
SELECT user_id, username, email, password, created_at, is_admin, display_name FROM users WHERE username = $1 OR email = $2 LIMIT 1
|
||||
SELECT user_id, username, email, password, created_at, is_admin FROM users WHERE username = $1 OR email = $2 LIMIT 1
|
||||
`
|
||||
|
||||
type GetUserByEmailOrNicknameParams struct {
|
||||
@ -114,13 +112,12 @@ func (q *Queries) GetUserByEmailOrNickname(ctx context.Context, arg GetUserByEma
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserById = `-- name: GetUserById :one
|
||||
SELECT user_id, username, email, password, created_at, is_admin, display_name FROM users WHERE user_id = $1
|
||||
SELECT user_id, username, email, password, created_at, is_admin FROM users WHERE user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserById(ctx context.Context, userID int64) (User, error) {
|
||||
@ -133,13 +130,12 @@ func (q *Queries) GetUserById(ctx context.Context, userID int64) (User, error) {
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByUsername = `-- name: GetUserByUsername :one
|
||||
SELECT user_id, username, email, password, created_at, is_admin, display_name FROM users WHERE username = $1
|
||||
SELECT user_id, username, email, password, created_at, is_admin FROM users WHERE username = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User, error) {
|
||||
@ -152,7 +148,6 @@ func (q *Queries) GetUserByUsername(ctx context.Context, username string) (User,
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@ -172,7 +167,7 @@ const updateUserPasswordHash = `-- name: UpdateUserPasswordHash :one
|
||||
UPDATE public.users
|
||||
SET "password"=$1
|
||||
WHERE user_id=$2
|
||||
RETURNING user_id, username, email, password, created_at, is_admin, display_name
|
||||
RETURNING user_id, username, email, password, created_at, is_admin
|
||||
`
|
||||
|
||||
type UpdateUserPasswordHashParams struct {
|
||||
@ -190,7 +185,6 @@ func (q *Queries) UpdateUserPasswordHash(ctx context.Context, arg UpdateUserPass
|
||||
&i.Password,
|
||||
&i.CreatedAt,
|
||||
&i.IsAdmin,
|
||||
&i.DisplayName,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
DROP TABLE IF EXISTS users_badges;
|
||||
|
||||
DROP TABLE IF EXISTS badges;
|
||||
@ -1,16 +0,0 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.badges(
|
||||
id uuid PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR(32)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.users_badges(
|
||||
user_id BIGINT NOT NULL,
|
||||
badge_id uuid NOT NULL,
|
||||
constraint users_badges_pkey primary key (user_id, badge_id),
|
||||
CONSTRAINT "user_id_badge_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON DELETE CASCADE,
|
||||
CONSTRAINT "badge_id_user_id_fkey" FOREIGN KEY ("badge_id") REFERENCES "public"."badges" ("id") ON DELETE CASCADE
|
||||
);
|
||||
@ -1,19 +0,0 @@
|
||||
-- Drop the explicit index (if not dropped automatically with the table)
|
||||
DROP INDEX IF EXISTS "public"."profiles_user_id_idx";
|
||||
|
||||
-- Drop tables in reverse order to avoid foreign key conflicts
|
||||
DROP TABLE IF EXISTS "public"."profiles" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."post_votes" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."post_tags" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."tags" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."likes" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."favorites" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."comments" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."bookmarks" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."posts" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."blogs" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."users" CASCADE;
|
||||
DROP TABLE IF EXISTS "public"."categories" CASCADE;
|
||||
|
||||
-- Optionally, if the up migration created the schema (and you want to remove it):
|
||||
-- DROP SCHEMA IF EXISTS "public" CASCADE;
|
||||
@ -1,121 +0,0 @@
|
||||
CREATE SCHEMA IF NOT EXISTS "public";
|
||||
|
||||
-- Create "categories" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."categories" (
|
||||
"category_id" integer NOT NULL,
|
||||
"category_name" character varying(50) NOT NULL,
|
||||
PRIMARY KEY ("category_id"),
|
||||
CONSTRAINT "categories_category_name_key" UNIQUE ("category_name")
|
||||
);
|
||||
-- Create "users" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."users" (
|
||||
"user_id" bigint NOT NULL,
|
||||
"username" character varying(50) NOT NULL,
|
||||
"email" character varying(100) NOT NULL,
|
||||
"password" character varying(255) NOT NULL,
|
||||
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"is_admin" boolean NOT NULL,
|
||||
"display_name" character varying(32) NULL,
|
||||
PRIMARY KEY ("user_id"),
|
||||
CONSTRAINT "users_email_key" UNIQUE ("email"),
|
||||
CONSTRAINT "users_username_key" UNIQUE ("username")
|
||||
);
|
||||
-- Create "blogs" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."blogs" (
|
||||
"blog_id" bigint NOT NULL,
|
||||
"user_id" bigint NOT NULL,
|
||||
"title" character varying(255) NULL,
|
||||
"description" text NULL,
|
||||
"category_id" integer NULL,
|
||||
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("blog_id"),
|
||||
CONSTRAINT "blogs_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "public"."categories" ("category_id") ON UPDATE NO ACTION ON DELETE NO ACTION,
|
||||
CONSTRAINT "blogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "posts" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."posts" (
|
||||
"post_id" bigint NOT NULL,
|
||||
"blog_id" bigint NULL,
|
||||
"user_id" bigint NOT NULL,
|
||||
"title" character varying(255) NULL,
|
||||
"content" text NULL,
|
||||
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("post_id"),
|
||||
CONSTRAINT "posts_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "bookmarks" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."bookmarks" (
|
||||
"user_id" bigint NOT NULL,
|
||||
"post_id" bigint NOT NULL,
|
||||
"bookmarked_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("user_id", "post_id"),
|
||||
CONSTRAINT "bookmarks_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "bookmarks_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "comments" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."comments" (
|
||||
"comment_id" bigint NOT NULL,
|
||||
"post_id" bigint NOT NULL,
|
||||
"user_id" bigint NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("comment_id"),
|
||||
CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "favorites" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."favorites" (
|
||||
"user_id" bigint NOT NULL,
|
||||
"blog_id" bigint NOT NULL,
|
||||
"favorited_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("user_id", "blog_id"),
|
||||
CONSTRAINT "favorites_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "favorites_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "likes" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."likes" (
|
||||
"like_id" bigint NOT NULL,
|
||||
"user_id" bigint NULL,
|
||||
"comment_id" bigint NULL,
|
||||
"created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY ("like_id"),
|
||||
CONSTRAINT "likes_comment_id_fkey" FOREIGN KEY ("comment_id") REFERENCES "public"."comments" ("comment_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "likes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "tags" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."tags" (
|
||||
"tag_id" integer NOT NULL,
|
||||
"tag_name" character varying(50) NOT NULL,
|
||||
PRIMARY KEY ("tag_id"),
|
||||
CONSTRAINT "tags_tag_name_key" UNIQUE ("tag_name")
|
||||
);
|
||||
-- Create "post_tags" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."post_tags" (
|
||||
"post_id" bigint NOT NULL,
|
||||
"tag_id" integer NOT NULL,
|
||||
PRIMARY KEY ("post_id", "tag_id"),
|
||||
CONSTRAINT "post_tags_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "post_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "public"."tags" ("tag_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create "post_votes" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."post_votes" (
|
||||
"post_id" bigint NOT NULL,
|
||||
"user_id" bigint NOT NULL,
|
||||
"vote" boolean NOT NULL,
|
||||
PRIMARY KEY ("post_id", "user_id"),
|
||||
CONSTRAINT "post_votes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE,
|
||||
CONSTRAINT "post_votes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE NO ACTION
|
||||
);
|
||||
-- Create "profiles" table
|
||||
CREATE TABLE IF NOT EXISTS "public"."profiles" (
|
||||
"user_id" bigint NOT NULL,
|
||||
"bio" text NULL,
|
||||
"avatar_url" character varying(255) NULL,
|
||||
"website_url" character varying(100) NULL,
|
||||
PRIMARY KEY ("user_id"),
|
||||
CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE
|
||||
);
|
||||
-- Create index "profiles_user_id_idx" to table: "profiles"
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "profiles_user_id_idx" ON "public"."profiles" ("user_id");
|
||||
@ -1,2 +0,0 @@
|
||||
ALTER TABLE profiles
|
||||
DROP COLUMN email_verified;
|
||||
@ -1,2 +0,0 @@
|
||||
ALTER TABLE profiles
|
||||
ADD COLUMN email_verified BOOLEAN DEFAULT FALSE;
|
||||
30
enshi_back/db/migrations/migration1.sql
Normal file
30
enshi_back/db/migrations/migration1.sql
Normal file
@ -0,0 +1,30 @@
|
||||
-- Add new schema named "public"
|
||||
CREATE SCHEMA IF NOT EXISTS "public";
|
||||
-- Set comment to schema: "public"
|
||||
COMMENT ON SCHEMA "public" IS 'standard public schema';
|
||||
-- Create "categories" table
|
||||
CREATE TABLE "public"."categories" ("category_id" integer NOT NULL, "category_name" character varying(50) NOT NULL, PRIMARY KEY ("category_id"), CONSTRAINT "categories_category_name_key" UNIQUE ("category_name"));
|
||||
-- Create "users" table
|
||||
CREATE TABLE "public"."users" ("user_id" bigint NOT NULL, "username" character varying(50) NOT NULL, "email" character varying(100) NOT NULL, "password" character varying(255) NOT NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, "is_admin" boolean NOT NULL, PRIMARY KEY ("user_id"), CONSTRAINT "users_email_key" UNIQUE ("email"), CONSTRAINT "users_username_key" UNIQUE ("username"));
|
||||
-- Create "blogs" table
|
||||
CREATE TABLE "public"."blogs" ("blog_id" bigint NOT NULL, "user_id" bigint NOT NULL, "title" character varying(255) NULL, "description" text NULL, "category_id" integer NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("blog_id"), CONSTRAINT "blogs_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "public"."categories" ("category_id") ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT "blogs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "posts" table
|
||||
CREATE TABLE "public"."posts" ("post_id" bigint NOT NULL, "blog_id" bigint NULL, "user_id" bigint NOT NULL, "title" character varying(255) NULL, "content" text NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("post_id"), CONSTRAINT "posts_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "posts_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "bookmarks" table
|
||||
CREATE TABLE "public"."bookmarks" ("user_id" bigint NOT NULL, "post_id" bigint NOT NULL, "bookmarked_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("user_id", "post_id"), CONSTRAINT "bookmarks_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "bookmarks_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "comments" table
|
||||
CREATE TABLE "public"."comments" ("comment_id" bigint NOT NULL, "post_id" bigint NULL, "user_id" bigint NULL, "content" text NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("comment_id"), CONSTRAINT "comments_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "comments_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "favorites" table
|
||||
CREATE TABLE "public"."favorites" ("user_id" bigint NOT NULL, "blog_id" bigint NOT NULL, "favorited_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("user_id", "blog_id"), CONSTRAINT "favorites_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs" ("blog_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "favorites_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "likes" table
|
||||
CREATE TABLE "public"."likes" ("like_id" bigint NOT NULL, "user_id" bigint NULL, "comment_id" bigint NULL, "created_at" timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("like_id"), CONSTRAINT "likes_comment_id_fkey" FOREIGN KEY ("comment_id") REFERENCES "public"."comments" ("comment_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "likes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "tags" table
|
||||
CREATE TABLE "public"."tags" ("tag_id" integer NOT NULL, "tag_name" character varying(50) NOT NULL, PRIMARY KEY ("tag_id"), CONSTRAINT "tags_tag_name_key" UNIQUE ("tag_name"));
|
||||
-- Create "post_tags" table
|
||||
CREATE TABLE "public"."post_tags" ("post_id" bigint NOT NULL, "tag_id" integer NOT NULL, PRIMARY KEY ("post_id", "tag_id"), CONSTRAINT "post_tags_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE CASCADE, CONSTRAINT "post_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "public"."tags" ("tag_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create "post_votes" table
|
||||
CREATE TABLE "public"."post_votes" ("post_id" bigint NOT NULL, "user_id" bigint NOT NULL, "vote" boolean NOT NULL, PRIMARY KEY ("post_id", "user_id"), CONSTRAINT "post_votes_post_id_fkey" FOREIGN KEY ("post_id") REFERENCES "public"."posts" ("post_id") ON UPDATE NO ACTION ON DELETE NO ACTION, CONSTRAINT "post_votes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE NO ACTION);
|
||||
-- Create "profiles" table
|
||||
CREATE TABLE "public"."profiles" ("user_id" bigint NOT NULL, "bio" text NULL, "avatar_url" character varying(255) NULL, "website_url" character varying(100) NULL, PRIMARY KEY ("user_id"), CONSTRAINT "profiles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users" ("user_id") ON UPDATE NO ACTION ON DELETE CASCADE);
|
||||
-- Create index "profiles_user_id_idx" to table: "profiles"
|
||||
CREATE UNIQUE INDEX "profiles_user_id_idx" ON "public"."profiles" ("user_id");
|
||||
@ -1,61 +0,0 @@
|
||||
-- name: InsertBadge :one
|
||||
INSERT INTO public.badges (id, name, description, color)
|
||||
VALUES
|
||||
(
|
||||
uuid_generate_v4(),
|
||||
$1,
|
||||
$2,
|
||||
$3
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateBadge :one
|
||||
UPDATE public.badges
|
||||
SET
|
||||
name = $2,
|
||||
description = $3,
|
||||
color = $4
|
||||
WHERE
|
||||
id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteBadge :exec
|
||||
DELETE FROM public.badges
|
||||
WHERE
|
||||
id = $1;
|
||||
|
||||
-- name: GetAllBadges :many
|
||||
SELECT *
|
||||
FROM public.badges;
|
||||
|
||||
-- name: GetBadgeByID :one
|
||||
SELECT *
|
||||
FROM public.badges
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: AddBadgeToUser :one
|
||||
INSERT INTO public.users_badges (user_id, badge_id)
|
||||
VALUES
|
||||
(
|
||||
$1,
|
||||
$2
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: RemoveBadgeFromUser :exec
|
||||
DELETE FROM public.users_badges
|
||||
WHERE
|
||||
user_id = $1
|
||||
AND badge_id = $2;
|
||||
|
||||
-- name: GetUserBadges :many
|
||||
SELECT b.*
|
||||
FROM public.badges b
|
||||
JOIN public.users_badges ub ON b.id = ub.badge_id
|
||||
WHERE ub.user_id = $1;
|
||||
|
||||
-- name: GetUsersWithBadge :many
|
||||
SELECT u.*
|
||||
FROM public.users u
|
||||
JOIN public.users_badges ub ON u.user_id = ub.user_id
|
||||
WHERE ub.badge_id = $1;
|
||||
@ -20,7 +20,7 @@ SELECT comment_id, post_id, user_id, "content", created_at
|
||||
FROM public."comments"
|
||||
where public."comments".post_id = $1
|
||||
order by created_at ASC
|
||||
LIMIT 10 offset (sqlc.arg('offset') * 10);
|
||||
LIMIT 10 offset ($2 * 10);
|
||||
|
||||
-- name: UpdateCommentByCommentId :one
|
||||
UPDATE public."comments"
|
||||
@ -32,8 +32,3 @@ RETURNING *;
|
||||
SELECT comment_id, post_id, user_id, "content", created_at
|
||||
FROM public."comments"
|
||||
where public."comments".user_id = $1 and public."comments".post_id = $2;
|
||||
|
||||
-- name: GetCommentById :one
|
||||
SELECT comment_id, post_id, user_id, "content", created_at
|
||||
FROM public."comments"
|
||||
WHERE comment_id=$1;
|
||||
@ -35,42 +35,8 @@ SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
|
||||
WHERE post_id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetRandomPosts :one
|
||||
WITH all_posts AS (
|
||||
SELECT
|
||||
COUNT(*) AS post_count
|
||||
FROM
|
||||
public.posts
|
||||
),
|
||||
filtered_posts AS (
|
||||
SELECT
|
||||
ARRAY(
|
||||
SELECT
|
||||
json_build_object(
|
||||
'post_id', post_id::text, 'blog_id', blog_id::text,
|
||||
'user_id', user_id::text, 'title', title,
|
||||
'created_at', created_at, 'content', SUBSTRING(content FROM 1 FOR 300)
|
||||
)
|
||||
FROM
|
||||
public.posts
|
||||
ORDER BY
|
||||
created_at DESC
|
||||
LIMIT $1 OFFSET $3
|
||||
) as selected_posts
|
||||
)
|
||||
SELECT
|
||||
fp.selected_posts,
|
||||
(ap.post_count - ($2 + 1) * $1)::int > 0 as has_next_page,
|
||||
case
|
||||
when (ap.post_count - ($2 + 1) * $1)::int > 0
|
||||
then $2 + 1
|
||||
else -1
|
||||
end as next_page_index,
|
||||
case
|
||||
when (ap.post_count - ( $2 + 1 ) * $1 + 1 * $1)::int <= ap.post_count
|
||||
then $2 - 1
|
||||
else -1
|
||||
end as prev_page_index
|
||||
FROM
|
||||
filtered_posts fp,
|
||||
all_posts ap;
|
||||
-- name: GetRandomPosts :many
|
||||
SELECT post_id, blog_id, user_id, title, created_at
|
||||
FROM public.posts
|
||||
ORDER BY RANDOM()
|
||||
LIMIT $1;
|
||||
@ -8,6 +8,5 @@ RUN go build -o enshi_bin .
|
||||
|
||||
FROM alpine
|
||||
COPY --from=builder /enshi_app/enshi_bin /usr/local/bin/enshi_bin
|
||||
COPY --from=builder /enshi_app/utils /utils
|
||||
EXPOSE 9876
|
||||
ENTRYPOINT [ "/usr/local/bin/enshi_bin" ]
|
||||
@ -8,63 +8,38 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.12.10 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.3 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opentracing-contrib/go-gin v0.0.0-20241203023905-a5650667207a // indirect
|
||||
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/arch v0.14.0 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/grpc v1.71.0 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@ -1,65 +1,35 @@
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic v1.12.10 h1:uVCQr6oS5669E9ZVW0HyksTLfNS7Q/9hV6IVS4nEMsI=
|
||||
github.com/bytedance/sonic v1.12.10/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
|
||||
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@ -70,41 +40,23 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opentracing-contrib/go-gin v0.0.0-20241203023905-a5650667207a h1:knf+W5b/KLzKhne8yxsr+OlCM7P6xw4MDaF+EwcEiVU=
|
||||
github.com/opentracing-contrib/go-gin v0.0.0-20241203023905-a5650667207a/go.mod h1:YmKG+/LVbHV0tn+c5fMWHDl3oYf2DpZk5SqVJmyGkl0=
|
||||
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -112,7 +64,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
@ -120,94 +71,28 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0 h1:jj/B7eX95/mOxim9g9laNZkOHKz/XCHG0G410SntRy4=
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.60.0/go.mod h1:ZvRTVaYYGypytG0zRp2A60lpj//cMq3ZnxYdZaljVBM=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
|
||||
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@ -15,10 +15,6 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceName = os.Getenv("SERVICE_NAME")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
if err := env.LoadEnv("utils/secret.env"); err != nil {
|
||||
@ -33,11 +29,7 @@ func main() {
|
||||
defer db_connection.Dbx.Close()
|
||||
defer db_connection.Dbx_connection.Close(context.Background())
|
||||
|
||||
cleanup := routes.InitTracer()
|
||||
defer cleanup(context.Background())
|
||||
|
||||
router := gin.Default()
|
||||
// router.Use(otelgin.Middleware(serviceName))
|
||||
|
||||
f, err := os.OpenFile("gin.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
|
||||
@ -22,7 +22,7 @@ func TargetMiddleware() gin.HandlerFunc {
|
||||
case "POST":
|
||||
c.Set("target", POST)
|
||||
case "GET":
|
||||
c.Set("target", GET)
|
||||
c.Set("target", DELETE)
|
||||
}
|
||||
|
||||
c.Next()
|
||||
|
||||
@ -16,7 +16,7 @@ func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
|
||||
claims, exists := c.Get(global.ContextTokenData)
|
||||
|
||||
if !exists {
|
||||
return auth.UserInfoJWT{}, fmt.Errorf("error getting user id 1")
|
||||
return auth.UserInfoJWT{}, fmt.Errorf("error getting user id")
|
||||
}
|
||||
|
||||
parsedUserId, err := strconv.ParseInt(
|
||||
|
||||
@ -12,7 +12,7 @@ func GetUserIdFromContext(c *gin.Context) (int64, error) {
|
||||
userId, exists := c.Get(global.ContextUserId)
|
||||
|
||||
if !exists {
|
||||
return -1, fmt.Errorf("error getting user id 2")
|
||||
return -1, fmt.Errorf("error getting user id")
|
||||
}
|
||||
|
||||
if parsedUserId, err := strconv.ParseInt(userId.(string), 10, 64); err != nil {
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
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/google/uuid"
|
||||
)
|
||||
|
||||
func CreateComment(c *gin.Context) {
|
||||
var CommentParams db_repo.CreateCommentParams
|
||||
if err := c.BindJSON(&CommentParams); err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
userId, err := getters.GetUserIdFromContext(c)
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if commentId, err := uuid.NewV7(); err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
} else {
|
||||
CommentParams.CommentID = -int64(binary.BigEndian.Uint64(commentId[8:]))
|
||||
}
|
||||
|
||||
CommentParams.UserID = userId
|
||||
|
||||
postId, err := getters.GetInt64Param(c, "post-id")
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
CommentParams.PostID = postId
|
||||
|
||||
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).CreateComment(
|
||||
context.Background(),
|
||||
CommentParams,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
transaction.Commit(context.Background())
|
||||
rest_api_stuff.OkAnswer(c, "comment has been created")
|
||||
}
|
||||
@ -1,42 +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"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetCommentsForPost(c *gin.Context) {
|
||||
var params db_repo.GetCommentsForPostAscParams
|
||||
|
||||
postId, err := getters.GetInt64Param(c, "post-id")
|
||||
if err != nil {
|
||||
rest_api_stuff.BadRequestAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(c.DefaultQuery("offset", "10"))
|
||||
if err != nil {
|
||||
params.Offset = int32(0)
|
||||
}
|
||||
|
||||
params.Offset = int32(offset)
|
||||
params.PostID = postId
|
||||
|
||||
comments, err := db_repo.New(db_connection.Dbx).GetCommentsForPostAsc(
|
||||
context.Background(),
|
||||
params,
|
||||
)
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, comments)
|
||||
}
|
||||
@ -14,8 +14,6 @@ const (
|
||||
POST_BLOG_MIDDLEWARE = "POST_BLOG_MIDDLEWARE"
|
||||
POST_VOTE_MIDDLEWARE = "POST_VOTE_MIDDLEWARE"
|
||||
POST_VOTES_MIDDLEWARE = "POST_VOTES_MIDDLEWARE"
|
||||
USER_MIDDLEWARE = "USER_MIDDLEWARE"
|
||||
COMMENT_MIDDLEWARE = "COMMENT_MIDDLEWARE"
|
||||
)
|
||||
|
||||
var MiddlewareProvider = middleware.MiddlewareProvider{
|
||||
@ -154,45 +152,6 @@ var policiesToRegister = map[string]middleware.RulesToCheck{
|
||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
},
|
||||
},
|
||||
|
||||
USER_MIDDLEWARE: {
|
||||
middleware.GET: {
|
||||
Rules: make([]rules.RuleFunction, 0),
|
||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
},
|
||||
middleware.PUT: {
|
||||
Rules: []rules.RuleFunction{
|
||||
globalrules.AuthorizedRule,
|
||||
},
|
||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
},
|
||||
middleware.DELETE: {
|
||||
Rules: []rules.RuleFunction{
|
||||
globalrules.IsAdminRule,
|
||||
},
|
||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
},
|
||||
},
|
||||
|
||||
COMMENT_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.DELETE: {
|
||||
Rules: []rules.RuleFunction{
|
||||
globalrules.AuthorizedRule,
|
||||
globalrules.IsOwnerOfTheCommentRule,
|
||||
},
|
||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func InitMiddlewareProvider() {
|
||||
|
||||
@ -19,38 +19,25 @@ func GetRandomPost(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
offset, err := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
params := db_repo.GetRandomPostsParams{
|
||||
Column1: int32(limit),
|
||||
Column2: int32(offset),
|
||||
Offset: int32(offset * limit),
|
||||
}
|
||||
|
||||
postsData, err :=
|
||||
db_repo.New(db_connection.Dbx).
|
||||
GetRandomPosts(context.Background(), params)
|
||||
GetRandomPosts(context.Background(), int32(limit))
|
||||
|
||||
if err != nil {
|
||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// result := make([]any, 0)
|
||||
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)),
|
||||
// })
|
||||
// }
|
||||
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, postsData)
|
||||
c.IndentedJSON(http.StatusOK, result)
|
||||
|
||||
}
|
||||
|
||||
@ -1,39 +1,19 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"enshi/middleware"
|
||||
"enshi/middleware/getters"
|
||||
"enshi/routes/authRoutes"
|
||||
"enshi/routes/blogRoutes"
|
||||
bookmarksroutes "enshi/routes/bookmarksRoutes"
|
||||
routes "enshi/routes/commentRoutes"
|
||||
"enshi/routes/postsRoutes"
|
||||
"enshi/routes/userProfileRoutes"
|
||||
userroutes "enshi/routes/userRoutes"
|
||||
voteroutes "enshi/routes/voteRoutes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
||||
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
|
||||
"go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
)
|
||||
|
||||
var (
|
||||
serviceName = os.Getenv("SERVICE_NAME")
|
||||
collectorURL = os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
|
||||
insecure = os.Getenv("INSECURE_MODE")
|
||||
)
|
||||
|
||||
func testCookie(c *gin.Context) {
|
||||
@ -62,53 +42,9 @@ func testAuth(c *gin.Context) {
|
||||
)
|
||||
}
|
||||
|
||||
func InitTracer() func(context.Context) error {
|
||||
|
||||
var secureOption otlptracegrpc.Option
|
||||
|
||||
if strings.ToLower(insecure) == "false" || insecure == "0" || strings.ToLower(insecure) == "f" {
|
||||
secureOption = otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, ""))
|
||||
} else {
|
||||
secureOption = otlptracegrpc.WithInsecure()
|
||||
}
|
||||
|
||||
exporter, err := otlptrace.New(
|
||||
context.Background(),
|
||||
otlptracegrpc.NewClient(
|
||||
secureOption,
|
||||
otlptracegrpc.WithEndpoint(collectorURL),
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create exporter: %v", err)
|
||||
}
|
||||
resources, err := resource.New(
|
||||
context.Background(),
|
||||
resource.WithAttributes(
|
||||
attribute.String("service.name", serviceName),
|
||||
attribute.String("library.language", "go"),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not set resources: %v", err)
|
||||
}
|
||||
|
||||
otel.SetTracerProvider(
|
||||
sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||
sdktrace.WithBatcher(exporter),
|
||||
sdktrace.WithResource(resources),
|
||||
),
|
||||
)
|
||||
return exporter.Shutdown
|
||||
}
|
||||
|
||||
func SetupRotes(g *gin.Engine) error {
|
||||
InitMiddlewareProvider()
|
||||
|
||||
fmt.Println(serviceName, collectorURL, insecure)
|
||||
|
||||
g.Use(middleware.CORSMiddleware())
|
||||
g.Use(middleware.TargetMiddleware())
|
||||
|
||||
@ -258,14 +194,6 @@ func SetupRotes(g *gin.Engine) error {
|
||||
voteroutes.GetVotes,
|
||||
)
|
||||
|
||||
userGroup := g.Group("/users/")
|
||||
userGroup.Use(MiddlewareProvider.GetMiddleware(USER_MIDDLEWARE))
|
||||
|
||||
userGroup.GET(
|
||||
"/info/:user-id",
|
||||
userroutes.GetUserInfo,
|
||||
)
|
||||
|
||||
// Admin group routes
|
||||
adminGroup := g.Group("/admin/")
|
||||
adminGroup.Use(middleware.AdminMiddleware())
|
||||
@ -281,19 +209,6 @@ func SetupRotes(g *gin.Engine) error {
|
||||
authRoutes.Logout,
|
||||
)
|
||||
|
||||
commentGroup := g.Group("/comments/")
|
||||
commentGroup.Use(MiddlewareProvider.GetMiddleware(COMMENT_MIDDLEWARE))
|
||||
|
||||
commentGroup.GET(
|
||||
":post-id",
|
||||
routes.GetCommentsForPost,
|
||||
)
|
||||
|
||||
commentGroup.POST(
|
||||
":post-id",
|
||||
routes.CreateComment,
|
||||
)
|
||||
|
||||
temporal := g.Group("/")
|
||||
temporal.Use(middleware.AuthMiddleware())
|
||||
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
package userroutes
|
||||
|
||||
import (
|
||||
"context"
|
||||
db_repo "enshi/db/go_queries"
|
||||
"enshi/db_connection"
|
||||
"enshi/middleware/getters"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetUserInfo(c *gin.Context) {
|
||||
userId, err := getters.GetInt64Param(c, "user-id")
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "Invalid user ID"})
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := db_repo.New(db_connection.Dbx).GetUserById(context.Background(), userId)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "Failed to retrieve user information"})
|
||||
return
|
||||
}
|
||||
|
||||
userProfileInfo, err := db_repo.New(db_connection.Dbx).GetProfileByUserId(context.Background(), userId)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "Failed to retrieve user profile information"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"user_info": userInfo,
|
||||
"profile_info": userProfileInfo,
|
||||
})
|
||||
}
|
||||
@ -1,562 +0,0 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"enshi/db_connection"
|
||||
"enshi/env"
|
||||
"enshi/routes"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var router *gin.Engine
|
||||
|
||||
// SetupRouter initializes the router with route groups for testing
|
||||
func SetupRouter() (*gin.Engine, error) {
|
||||
router := gin.Default()
|
||||
err := routes.SetupRotes(router)
|
||||
return router, err
|
||||
}
|
||||
|
||||
// TestMain sets up the global router before running tests
|
||||
func TestMain(m *testing.M) {
|
||||
if err := env.LoadEnv("../utils/secret.env"); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := db_connection.SetupDatabase(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
defer db_connection.Dbx.Close()
|
||||
defer db_connection.Dbx_connection.Close(context.Background())
|
||||
|
||||
var err error
|
||||
router, err = SetupRouter()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Run all tests
|
||||
exitCode := m.Run()
|
||||
|
||||
// Exit with the appropriate status code
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
// TestRouterSetup verifies that the router is set up correctly
|
||||
func TestRouterSetup(t *testing.T) {
|
||||
if router == nil {
|
||||
t.Fatalf("Router is not initialized")
|
||||
}
|
||||
t.Log("Routes setup successfully")
|
||||
}
|
||||
|
||||
// TestGetRandomPost tests the /posts/random endpoint
|
||||
func TestGetRandomPost(t *testing.T) {
|
||||
t.Log("router in here", router)
|
||||
req, _ := http.NewRequest("GET", "/test/posts/random", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Use the global router to serve the request
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// Check the response status code
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetRandomPost endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetRandomPost_WithLimit проверяет успешный запрос с указанным лимитом постов.
|
||||
func TestGetRandomPost_WithLimit(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/test/posts/random?limit=5", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetRandomPost with limit endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetRandomPost_InvalidLimit проверяет запрос с недопустимым лимитом.
|
||||
func TestGetRandomPost_InvalidLimit(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/test/posts/random?limit=invalid", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusInternalServerError, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetRandomPost with invalid limit returned Internal Server Error as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPost_Success проверяет успешное получение поста по корректному ID.
|
||||
func TestGetPost_Success(t *testing.T) {
|
||||
// Предполагается, что пост с ID 1 существует в тестовой базе данных
|
||||
postID := "5984625340089268323"
|
||||
req, _ := http.NewRequest("GET", "/test/posts/"+postID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetPost endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPost_InvalidID проверяет запрос с некорректным форматом ID.
|
||||
func TestGetPost_InvalidID(t *testing.T) {
|
||||
invalidPostID := "abc"
|
||||
req, _ := http.NewRequest("GET", "/test/posts/"+invalidPostID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetPost with invalid ID returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPost_NotFound проверяет запрос к несуществующему посту.
|
||||
func TestGetPost_NotFound(t *testing.T) {
|
||||
// Предполагается, что пост с ID 9999 не существует
|
||||
nonExistentPostID := "9999"
|
||||
req, _ := http.NewRequest("GET", "/test/posts/"+nonExistentPostID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusInternalServerError, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetPost with non-existent ID returned Internal Server Error as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBlog_Success проверяет успешное создание блога.
|
||||
func TestCreateBlog_Success(t *testing.T) {
|
||||
// Создаем тестовые данные для блога
|
||||
blogData := map[string]interface{}{
|
||||
"user_id": 1,
|
||||
"title": "Test Blog",
|
||||
"description": "A blog for testing",
|
||||
"category_id": 2,
|
||||
}
|
||||
body, _ := json.Marshal(blogData)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/test/blogs", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusCreated, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("CreateBlog endpoint returned Created")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBlog_InvalidData проверяет создание блога с некорректными данными.
|
||||
func TestCreateBlog_InvalidData(t *testing.T) {
|
||||
// Отсутствует обязательное поле user_id
|
||||
blogData := map[string]interface{}{
|
||||
"title": "Test Blog",
|
||||
"description": "A blog with invalid data",
|
||||
}
|
||||
body, _ := json.Marshal(blogData)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/test/blogs", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("CreateBlog with invalid data returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBlogsByUser_Success проверяет успешное получение блогов по user_id.
|
||||
func TestGetBlogsByUser_Success(t *testing.T) {
|
||||
userID := 1
|
||||
req, _ := http.NewRequest("GET", "/test/users/"+strconv.Itoa(userID)+"/blogs", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBlogsByUser endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBlog_Success проверяет успешное получение блога по blog_id.
|
||||
func TestGetBlog_Success(t *testing.T) {
|
||||
// Предполагается, что блог с ID 1 существует
|
||||
blogID := "1"
|
||||
req, _ := http.NewRequest("GET", "/test/blogs/"+blogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBlog endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBlog_InvalidID проверяет получение блога с некорректным blog_id.
|
||||
func TestGetBlog_InvalidID(t *testing.T) {
|
||||
invalidBlogID := "abc"
|
||||
req, _ := http.NewRequest("GET", "/test/blogs/"+invalidBlogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBlog with invalid ID returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBlog_NotFound проверяет получение несуществующего блога.
|
||||
func TestGetBlog_NotFound(t *testing.T) {
|
||||
// Предполагается, что блог с ID 9999 не существует
|
||||
nonExistentBlogID := "9999"
|
||||
req, _ := http.NewRequest("GET", "/test/blogs/"+nonExistentBlogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBlog with non-existent ID returned Not Found as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateBlog_Success проверяет успешное обновление блога.
|
||||
func TestUpdateBlog_Success(t *testing.T) {
|
||||
// Предполагается, что блог с ID 1 существует
|
||||
blogID := "1"
|
||||
updateData := map[string]interface{}{
|
||||
"title": "Updated Test Blog",
|
||||
"description": "Updated description",
|
||||
"category_id": 3,
|
||||
}
|
||||
body, _ := json.Marshal(updateData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/test/blogs/"+blogID, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("UpdateBlog endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateBlog_InvalidData проверяет обновление блога с некорректными данными.
|
||||
func TestUpdateBlog_InvalidData(t *testing.T) {
|
||||
// Предполагается, что блог с ID 1 существует
|
||||
blogID := "1"
|
||||
// Отсутствует обязательное поле title
|
||||
updateData := map[string]interface{}{
|
||||
"description": "Updated description without title",
|
||||
}
|
||||
body, _ := json.Marshal(updateData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/test/blogs/"+blogID, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("UpdateBlog with invalid data returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdateBlog_NotFound проверяет обновление несуществующего блога.
|
||||
func TestUpdateBlog_NotFound(t *testing.T) {
|
||||
nonExistentBlogID := "9999"
|
||||
updateData := map[string]interface{}{
|
||||
"title": "Non-existent Blog",
|
||||
"description": "Trying to update a blog that does not exist",
|
||||
"category_id": 4,
|
||||
}
|
||||
body, _ := json.Marshal(updateData)
|
||||
|
||||
req, _ := http.NewRequest("PUT", "/test/blogs/"+nonExistentBlogID, bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("UpdateBlog for non-existent blog returned Not Found as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBlog_Success проверяет успешное удаление блога.
|
||||
func TestDeleteBlog_Success(t *testing.T) {
|
||||
// Предполагается, что блог с ID 2 существует и может быть удален
|
||||
blogID := "2"
|
||||
req, _ := http.NewRequest("DELETE", "/test/blogs/"+blogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBlog endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBlog_InvalidID проверяет удаление блога с некорректным blog_id.
|
||||
func TestDeleteBlog_InvalidID(t *testing.T) {
|
||||
invalidBlogID := "xyz"
|
||||
req, _ := http.NewRequest("DELETE", "/test/blogs/"+invalidBlogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBlog with invalid ID returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBlog_NotFound проверяет удаление несуществующего блога.
|
||||
func TestDeleteBlog_NotFound(t *testing.T) {
|
||||
nonExistentBlogID := "9999"
|
||||
req, _ := http.NewRequest("DELETE", "/test/blogs/"+nonExistentBlogID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBlog for non-existent blog returned Not Found as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBookmark_Success проверяет успешное создание закладки.
|
||||
func TestCreateBookmark_Success(t *testing.T) {
|
||||
bookmarkData := map[string]interface{}{
|
||||
"user_id": 1,
|
||||
"post_id": 1,
|
||||
}
|
||||
body, _ := json.Marshal(bookmarkData)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/test/bookmarks", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusCreated, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("CreateBookmark endpoint returned Created")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreateBookmark_InvalidData проверяет создание закладки с некорректными данными.
|
||||
func TestCreateBookmark_InvalidData(t *testing.T) {
|
||||
// Отсутствует обязательное поле post_id
|
||||
bookmarkData := map[string]interface{}{
|
||||
"user_id": 1,
|
||||
}
|
||||
body, _ := json.Marshal(bookmarkData)
|
||||
|
||||
req, _ := http.NewRequest("POST", "/test/bookmarks", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("CreateBookmark with invalid data returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookmarksByUser_Success проверяет успешное получение закладок по user_id.
|
||||
func TestGetBookmarksByUser_Success(t *testing.T) {
|
||||
userID := 1
|
||||
req, _ := http.NewRequest("GET", "/test/users/"+strconv.Itoa(userID)+"/bookmarks", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBookmarksByUser endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookmarksByUser_InvalidUserID проверяет получение закладок с некорректным user_id.
|
||||
func TestGetBookmarksByUser_InvalidUserID(t *testing.T) {
|
||||
invalidUserID := "abc"
|
||||
req, _ := http.NewRequest("GET", "/test/users/"+invalidUserID+"/bookmarks", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBookmarksByUser with invalid user ID returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBookmark_Success проверяет успешное удаление закладки.
|
||||
func TestDeleteBookmark_Success(t *testing.T) {
|
||||
// Предполагается, что закладка с user_id=1 и post_id=2 существует
|
||||
userID := "1"
|
||||
postID := "2"
|
||||
req, _ := http.NewRequest("DELETE", "/test/bookmarks/"+userID+"/"+postID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBookmark endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBookmark_InvalidData проверяет удаление закладки с некорректными данными.
|
||||
func TestDeleteBookmark_InvalidData(t *testing.T) {
|
||||
invalidUserID := "xyz"
|
||||
invalidPostID := "abc"
|
||||
req, _ := http.NewRequest("DELETE", "/test/bookmarks/"+invalidUserID+"/"+invalidPostID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBookmark with invalid data returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBookmark_NotFound проверяет удаление несуществующей закладки.
|
||||
func TestDeleteBookmark_NotFound(t *testing.T) {
|
||||
nonExistentUserID := "9999"
|
||||
nonExistentPostID := "9999"
|
||||
req, _ := http.NewRequest("DELETE", "/test/bookmarks/"+nonExistentUserID+"/"+nonExistentPostID, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusNotFound, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("DeleteBookmark for non-existent bookmark returned Not Found as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCountOfBookmarksByPost_Success проверяет успешное получение количества закладок для поста.
|
||||
func TestGetCountOfBookmarksByPost_Success(t *testing.T) {
|
||||
postID := "1"
|
||||
req, _ := http.NewRequest("GET", "/test/posts/"+postID+"/bookmarks/count", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetCountOfBookmarksByPost endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetCountOfBookmarksByPost_InvalidPostID проверяет получение количества закладок с некорректным post_id.
|
||||
func TestGetCountOfBookmarksByPost_InvalidPostID(t *testing.T) {
|
||||
invalidPostID := "abc"
|
||||
req, _ := http.NewRequest("GET", "/test/posts/"+invalidPostID+"/bookmarks/count", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetCountOfBookmarksByPost with invalid post ID returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookmarkTimestamp_Success проверяет успешное получение временной метки закладки.
|
||||
func TestGetBookmarkTimestamp_Success(t *testing.T) {
|
||||
userID := "1"
|
||||
postID := "1"
|
||||
req, _ := http.NewRequest("GET", "/test/bookmarks/"+userID+"/"+postID+"/timestamp", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusOK, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBookmarkTimestamp endpoint returned OK")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetBookmarkTimestamp_InvalidData проверяет получение временной метки с некорректными данными.
|
||||
func TestGetBookmarkTimestamp_InvalidData(t *testing.T) {
|
||||
invalidUserID := "xyz"
|
||||
invalidPostID := "abc"
|
||||
req, _ := http.NewRequest("GET", "/test/bookmarks/"+invalidUserID+"/"+invalidPostID+"/timestamp", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("Expected status code %d but got %d. Body: %s", http.StatusBadRequest, w.Code, w.Body.String())
|
||||
} else {
|
||||
t.Log("GetBookmarkTimestamp with invalid data returned Bad Request as expected")
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user