Compare commits
No commits in common. "feature/userProfile" and "develop" have entirely different histories.
feature/us
...
develop
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -3,17 +3,5 @@
|
|||||||
"downvotes",
|
"downvotes",
|
||||||
"godotenv",
|
"godotenv",
|
||||||
"upvotes"
|
"upvotes"
|
||||||
],
|
|
||||||
"sqltools.connections": [
|
|
||||||
{
|
|
||||||
"previewLimit": 50,
|
|
||||||
"server": "nekiiinkognito.ru",
|
|
||||||
"port": 5432,
|
|
||||||
"askForPassword": true,
|
|
||||||
"driver": "PostgreSQL",
|
|
||||||
"name": "enshi",
|
|
||||||
"database": "postgres",
|
|
||||||
"username": "neki"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"useTabs": true,
|
"useTabs": false
|
||||||
"printWidth": 80,
|
|
||||||
"semi": true,
|
|
||||||
"quoteProps": "consistent"
|
|
||||||
}
|
}
|
||||||
|
|||||||
142
enshi/package-lock.json
generated
142
enshi/package-lock.json
generated
@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "enshi",
|
"name": "enshi",
|
||||||
"version": "0.1.8",
|
"version": "0.1.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "enshi",
|
"name": "enshi",
|
||||||
"version": "0.1.8",
|
"version": "0.1.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.2",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-form": "^0.1.0",
|
"@radix-ui/react-form": "^0.1.0",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
@ -19,9 +18,7 @@
|
|||||||
"@radix-ui/themes": "^3.1.3",
|
"@radix-ui/themes": "^3.1.3",
|
||||||
"@tanstack/react-query": "^5.55.0",
|
"@tanstack/react-query": "^5.55.0",
|
||||||
"@tanstack/react-query-devtools": "^5.61.0",
|
"@tanstack/react-query-devtools": "^5.61.0",
|
||||||
"@types/quill": "^2.0.14",
|
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"html-react-parser": "^5.1.16",
|
"html-react-parser": "^5.1.16",
|
||||||
"i18n": "^0.15.1",
|
"i18n": "^0.15.1",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
@ -31,11 +28,10 @@
|
|||||||
"jotai": "^2.9.3",
|
"jotai": "^2.9.3",
|
||||||
"jotai-immer": "^0.4.1",
|
"jotai-immer": "^0.4.1",
|
||||||
"primereact": "^10.8.2",
|
"primereact": "^10.8.2",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-intersection-observer": "^9.15.1",
|
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-router-dom": "^6.26.2"
|
"react-router-dom": "^6.26.2"
|
||||||
},
|
},
|
||||||
@ -1298,12 +1294,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@radix-ui/react-aspect-ratio": {
|
"node_modules/@radix-ui/react-aspect-ratio": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.0.tgz",
|
||||||
"integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==",
|
"integrity": "sha512-dP87DM/Y7jFlPgUZTlhx6FF5CEzOiaxp2rBCKlaXlpH5Ip/9Fg5zZ9lDOQ5o/MOfUlf36eak14zoWYpgcgGoOg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-primitive": "2.0.2"
|
"@radix-ui/react-primitive": "2.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "*",
|
"@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": {
|
"node_modules/@radix-ui/react-avatar": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz",
|
"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": {
|
"node_modules/@radix-ui/themes/node_modules/@radix-ui/react-dialog": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
|
||||||
@ -3505,13 +3422,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/quill": {
|
"node_modules/@types/quill": {
|
||||||
"version": "2.0.14",
|
"version": "1.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||||
"integrity": "sha512-zvoXCRnc2Dl8g+7/9VSAmRWPN6oH+MVhTPizmCR+GJCITplZ5VRVzMs4+a/nOE3yzNwEZqylJJrMB07bwbM1/g==",
|
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"parchment": "^1.1.2",
|
"parchment": "^1.1.2"
|
||||||
"quill-delta": "^5.1.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/quill/node_modules/parchment": {
|
"node_modules/@types/quill/node_modules/parchment": {
|
||||||
@ -4297,12 +4213,6 @@
|
|||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
@ -6499,9 +6409,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/quill": {
|
"node_modules/quill": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
|
||||||
"integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
|
"integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eventemitter3": "^5.0.1",
|
"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": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
@ -6616,15 +6511,6 @@
|
|||||||
"react-dom": "^16 || ^17 || ^18"
|
"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": {
|
"node_modules/react-quill/node_modules/eventemitter3": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
|
||||||
|
|||||||
@ -11,7 +11,6 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.2",
|
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.2",
|
||||||
"@radix-ui/react-form": "^0.1.0",
|
"@radix-ui/react-form": "^0.1.0",
|
||||||
"@radix-ui/react-icons": "^1.3.2",
|
"@radix-ui/react-icons": "^1.3.2",
|
||||||
@ -22,9 +21,7 @@
|
|||||||
"@radix-ui/themes": "^3.1.3",
|
"@radix-ui/themes": "^3.1.3",
|
||||||
"@tanstack/react-query": "^5.55.0",
|
"@tanstack/react-query": "^5.55.0",
|
||||||
"@tanstack/react-query-devtools": "^5.61.0",
|
"@tanstack/react-query-devtools": "^5.61.0",
|
||||||
"@types/quill": "^2.0.14",
|
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
"dayjs": "^1.11.13",
|
|
||||||
"html-react-parser": "^5.1.16",
|
"html-react-parser": "^5.1.16",
|
||||||
"i18n": "^0.15.1",
|
"i18n": "^0.15.1",
|
||||||
"i18next": "^23.14.0",
|
"i18next": "^23.14.0",
|
||||||
@ -34,11 +31,10 @@
|
|||||||
"jotai": "^2.9.3",
|
"jotai": "^2.9.3",
|
||||||
"jotai-immer": "^0.4.1",
|
"jotai-immer": "^0.4.1",
|
||||||
"primereact": "^10.8.2",
|
"primereact": "^10.8.2",
|
||||||
"quill": "^2.0.3",
|
"quill": "^2.0.2",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-i18next": "^15.0.1",
|
"react-i18next": "^15.0.1",
|
||||||
"react-intersection-observer": "^9.15.1",
|
|
||||||
"react-quill": "^2.0.0",
|
"react-quill": "^2.0.0",
|
||||||
"react-router-dom": "^6.26.2"
|
"react-router-dom": "^6.26.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,25 +4,5 @@ export type GetRandomPostsRow = {
|
|||||||
user_id: string;
|
user_id: string;
|
||||||
title: string;
|
title: string;
|
||||||
// created_at: Date;
|
// 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;
|
isAdmin: boolean;
|
||||||
id?: string | number;
|
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: '';
|
content: '';
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-toolbar {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ql-container {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ql-snow.ql-toolbar button,
|
.ql-snow.ql-toolbar button,
|
||||||
.ql-snow .ql-toolbar button {
|
.ql-snow .ql-toolbar button {
|
||||||
background: none;
|
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 "@radix-ui/themes/styles.css";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||||
import "axios";
|
import "axios";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
||||||
import { useAtomValue } from "jotai";
|
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import queryClient from "./api/QueryClient/QueryClient";
|
import queryClient from "./api/QueryClient/QueryClient";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { themeAtom } from "./AtomStore/AtomStore";
|
|
||||||
import ToastProvider from "./Components/ToastProvider/ToastProvider";
|
import ToastProvider from "./Components/ToastProvider/ToastProvider";
|
||||||
import { routes } from "./routes/routes";
|
import { routes } from "./routes/routes";
|
||||||
|
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
|
||||||
|
|
||||||
const router = createBrowserRouter(routes);
|
const router = createBrowserRouter(routes);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
||||||
const theme = useAtomValue(themeAtom);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Theme className="h-fit" accentColor="sky" grayColor="slate" appearance={theme}>
|
<Theme className="h-fit" accentColor="sky" grayColor="slate" appearance="dark">
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
{/* <ThemePanel /> */}
|
<ThemePanel />
|
||||||
<ReactQueryDevtools/>
|
<ReactQueryDevtools/>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { atom } from "jotai";
|
import { atom } from "jotai";
|
||||||
import { atomWithStorage } from "jotai/utils";
|
import { atomWithStorage } from "jotai/utils";
|
||||||
import { TPostData } from "../@types/PostTypes";
|
|
||||||
import { TUser } from "../@types/UserType";
|
import { TUser } from "../@types/UserType";
|
||||||
|
|
||||||
export const userAtom = atom<TUser>();
|
export const userAtom = atom<TUser>();
|
||||||
@ -8,18 +7,10 @@ export const userAtom = atom<TUser>();
|
|||||||
export const postCreationAtom = atom<string>();
|
export const postCreationAtom = atom<string>();
|
||||||
export const postCreationTitleAtom = atom<string>();
|
export const postCreationTitleAtom = atom<string>();
|
||||||
|
|
||||||
export const themeAtom = atomWithStorage<"light" | "dark">(
|
type TPostData = {
|
||||||
"theme",
|
title: string;
|
||||||
"light",
|
content: string;
|
||||||
{
|
};
|
||||||
getItem: (key) => localStorage.getItem(key) as any,
|
|
||||||
setItem: (key, value) => localStorage.setItem(key, value as any),
|
|
||||||
removeItem: (key) => localStorage.removeItem(key),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
getOnInit: true,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const storagePostAtom = atomWithStorage<TPostData>(
|
export const storagePostAtom = atomWithStorage<TPostData>(
|
||||||
"draft-post",
|
"draft-post",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
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 { useQuery } from "@tanstack/react-query";
|
||||||
import { Interweave } from "interweave";
|
import { Interweave } from "interweave";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
@ -35,14 +35,17 @@ export default function ArticleViewer(props: TArticleViewer) {
|
|||||||
if (isPending) return <SkeletonPostLoader />;
|
if (isPending) return <SkeletonPostLoader />;
|
||||||
|
|
||||||
return (
|
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"}>
|
<Text className="mb-2" as="div" size={"9"}>
|
||||||
{data.title}
|
{data.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Flex
|
<Flex
|
||||||
gap={"3"}
|
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"}>
|
<Flex gap={"1"}>
|
||||||
<VoteButton
|
<VoteButton
|
||||||
@ -50,7 +53,9 @@ export default function ArticleViewer(props: TArticleViewer) {
|
|||||||
postId={queryParams["postId"] || ""}
|
postId={queryParams["postId"] || ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VoteCounter postId={queryParams["postId"] || ""} />
|
<VoteCounter
|
||||||
|
postId={queryParams["postId"] || ""}
|
||||||
|
/>
|
||||||
|
|
||||||
<VoteButton
|
<VoteButton
|
||||||
vote={DOWNVOTE}
|
vote={DOWNVOTE}
|
||||||
@ -67,12 +72,11 @@ export default function ArticleViewer(props: TArticleViewer) {
|
|||||||
{user ? <AddPostToBlogDialog /> : null}
|
{user ? <AddPostToBlogDialog /> : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Separator size={"4"} className="mb-2" />
|
||||||
<Separator size={"4"} className="my-2" />
|
|
||||||
|
|
||||||
<Text>
|
|
||||||
<Interweave content={data.content} />
|
<Interweave content={data.content} />
|
||||||
</Text>
|
</Container>
|
||||||
</ScrollArea>
|
</div>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Delta } from "quill/core";
|
import Sources from "quill";
|
||||||
import { forwardRef } from "react";
|
import Quill, { Delta } from "quill/core";
|
||||||
|
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||||
|
import ReactQuill from "react-quill";
|
||||||
|
|
||||||
type TEditor = {
|
type TEditor = {
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
@ -28,55 +30,61 @@ const modules = {
|
|||||||
* @param onChange - function that accepts Delta element
|
* @param onChange - function that accepts Delta element
|
||||||
*/
|
*/
|
||||||
const Editor = forwardRef((props: TEditor) => {
|
const Editor = forwardRef((props: TEditor) => {
|
||||||
// const editor = useRef(null);
|
const editor = useRef(null);
|
||||||
// const [quill, setQuill] = useState<Quill | null>(null);
|
const [quill, setQuill] = useState<Quill | null>(null);
|
||||||
// const [value, setValue] = useState(new Delta());
|
const [value, setValue] = useState(new Delta());
|
||||||
|
|
||||||
// const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// if (editor.current) {
|
if (editor.current) {
|
||||||
// //@ts-ignore
|
//@ts-ignore
|
||||||
// const temp = editor.current.getEditor() as Quill;
|
const temp = editor.current.getEditor() as Quill;
|
||||||
// setQuill(temp);
|
setQuill(temp);
|
||||||
// }
|
}
|
||||||
// return () => {
|
return () => {
|
||||||
// setQuill(null);
|
setQuill(null);
|
||||||
// };
|
};
|
||||||
// }, [editor.current]);
|
}, [editor.current]);
|
||||||
|
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// const quill = new Quill(document.createElement("div"));
|
const quill = new Quill(document.createElement("div"));
|
||||||
// console.log(`AMOOOGUS`, props.defaultValue);
|
const t = quill.clipboard.convert({
|
||||||
|
html: props.defaultValue as string,
|
||||||
|
}) as Delta;
|
||||||
|
|
||||||
// const t = quill.clipboard.convert({
|
if (!loaded) {
|
||||||
// html: props.defaultValue as string,
|
setValue(t);
|
||||||
// }) as Delta;
|
|
||||||
|
|
||||||
// if (!loaded) {
|
console.log(t);
|
||||||
// setValue(t);
|
}
|
||||||
// console.log(t);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// setLoaded(true);
|
setLoaded(true);
|
||||||
// }, [props.defaultValue]);
|
}, [props.defaultValue]);
|
||||||
|
|
||||||
// const changeHandler = (
|
const changeHandler = (
|
||||||
// val: string,
|
val: string,
|
||||||
// _changeDelta: Delta,
|
_changeDelta: Delta,
|
||||||
// _source: Sources,
|
_source: Sources,
|
||||||
// _editor: ReactQuill.UnprivilegedEditor
|
_editor: ReactQuill.UnprivilegedEditor
|
||||||
// ) => {
|
) => {
|
||||||
// console.log(val);
|
console.log(val);
|
||||||
// console.log(JSON.stringify(quill?.getContents().ops, null, 2));
|
console.log(JSON.stringify(quill?.getContents().ops, null, 2));
|
||||||
// let fullDelta = quill?.getContents();
|
let fullDelta = quill?.getContents();
|
||||||
// if (props.onChange) props.onChange(val || "");
|
if (props.onChange) props.onChange(val || "");
|
||||||
// if (loaded) setValue(fullDelta || new Delta());
|
if (loaded) setValue(fullDelta || new Delta());
|
||||||
// };
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-editor h-[400px]">
|
<div className="text-editor">
|
||||||
DEPRECATED
|
<ReactQuill
|
||||||
|
value={value}
|
||||||
|
ref={editor}
|
||||||
|
modules={modules}
|
||||||
|
onChange={changeHandler}
|
||||||
|
theme="snow"
|
||||||
|
placeholder="Type your thoughts here..."
|
||||||
|
/>
|
||||||
</div>
|
</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 CustomNavigationMenu from "./NavigationMenu/NavigationMenu";
|
||||||
import RightButtonBar from "./RightButtonBar/RightButtonBar";
|
import RightButtonBar from "./RightButtonBar/RightButtonBar";
|
||||||
import SearchField from "./SearchField/SearchField";
|
import SearchField from "./SearchField/SearchField";
|
||||||
|
|
||||||
export default function NavBar() {
|
export default function NavBar() {
|
||||||
return (
|
return (
|
||||||
<Flex className="justify-center w-full">
|
<nav className="flex justify-center pt-2 pb-2 ml-4 mr-4 flex-[1] max-h-fit">
|
||||||
<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">
|
|
||||||
<CustomNavigationMenu />
|
<CustomNavigationMenu />
|
||||||
|
|
||||||
<SearchField />
|
<SearchField />
|
||||||
|
|
||||||
<RightButtonBar />
|
<RightButtonBar />
|
||||||
</nav>
|
</nav>
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ export default function CustomNavigationMenu() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<NavigationMenu.Root orientation="horizontal" className="h-full">
|
<NavigationMenu.Root orientation="horizontal">
|
||||||
<NavigationMenu.List className="flex items-center justify-start h-full gap-8 my-auto">
|
<NavigationMenu.List className="flex items-center justify-start gap-8">
|
||||||
<NavItem text={t("home")} to="/" />
|
<NavItem text={t("home")} to="/" />
|
||||||
|
|
||||||
<NavItem text={t("following")} to="/c" />
|
<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 { Button, Text } from "@radix-ui/themes";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -8,8 +8,8 @@ export default function CreatePostButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={"/create"}>
|
<Link to={"/create"}>
|
||||||
<Button variant="ghost" className="items-center h-full px-[6px] pr-[8px] py-0 my-auto m-0 overflow-hidden">
|
<Button variant="ghost" className="h-full">
|
||||||
<Pencil1Icon />
|
<PlusIcon />
|
||||||
<Text>{t("createPost")}</Text>
|
<Text>{t("createPost")}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
import CreatePostButton from "./CreatePostButton/CreatePostButton";
|
import CreatePostButton from "./CreatePostButton/CreatePostButton";
|
||||||
import ThemeChangeButton from "./ThemeChangeButton/ThemeChangeButton";
|
|
||||||
import UserButton from "./UserButton/UserButton";
|
import UserButton from "./UserButton/UserButton";
|
||||||
|
|
||||||
|
|
||||||
export default function RightButtonBar() {
|
export default function RightButtonBar() {
|
||||||
return (
|
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 />
|
<CreatePostButton />
|
||||||
<ThemeChangeButton />
|
|
||||||
<UserButton />
|
<UserButton />
|
||||||
</div>
|
</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();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={"/login"} className="size-full">
|
<Link to={"/login"}>
|
||||||
<Flex className="items-center justify-start h-full gap-2">
|
<Flex className="justify-between gap-2">
|
||||||
<Icon>
|
<Icon>
|
||||||
<EnterIcon />
|
<EnterIcon />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export default function LogoutButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
className="items-center justify-start gap-2 cursor-pointer size-full"
|
className="justify-between gap-2"
|
||||||
onClick={() => logoutMutation.mutate()}
|
onClick={() => logoutMutation.mutate()}
|
||||||
>
|
>
|
||||||
<Icon>
|
<Icon>
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import { LaptopIcon, PersonIcon } from "@radix-ui/react-icons";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
LaptopIcon,
|
||||||
Flex,
|
PersonIcon
|
||||||
IconButton,
|
} from "@radix-ui/react-icons";
|
||||||
Text,
|
import { DropdownMenu, Flex, IconButton, Text } from "@radix-ui/themes";
|
||||||
Tooltip,
|
|
||||||
} from "@radix-ui/themes";
|
|
||||||
import { Icon } from "@radix-ui/themes/dist/esm/components/callout.js";
|
import { Icon } from "@radix-ui/themes/dist/esm/components/callout.js";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { userAtom } from "../../../../AtomStore/AtomStore";
|
import { userAtom } from "../../../../AtomStore/AtomStore";
|
||||||
import LoginButton from "./LoginButton/LoginButton";
|
import LoginButton from "./LoginButton/LoginButton";
|
||||||
import LogoutButton from "./LogoutButton/LogoutButton";
|
import LogoutButton from "./LogoutButton/LogoutButton";
|
||||||
@ -18,51 +15,47 @@ export default function UserButton() {
|
|||||||
const user = useAtomValue(userAtom);
|
const user = useAtomValue(userAtom);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<DropdownMenu.Root>
|
<DropdownMenu.Root>
|
||||||
<Tooltip content={"User menu"} className="w-fit">
|
|
||||||
<DropdownMenu.Trigger>
|
<DropdownMenu.Trigger>
|
||||||
<IconButton
|
<IconButton className="cursor-pointer">
|
||||||
size={"3"}
|
<PersonIcon />
|
||||||
className="items-center my-auto rounded-full"
|
|
||||||
variant="ghost"
|
|
||||||
>
|
|
||||||
<PersonIcon className="size-6" />
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</DropdownMenu.Trigger>
|
</DropdownMenu.Trigger>
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<DropdownMenu.Content className="w-fit">
|
<DropdownMenu.Content className="w-fit">
|
||||||
<DropdownMenu.Item onClick={() => navigate("/profile")}>
|
<DropdownMenu.Item>
|
||||||
<div>
|
<Link to={"/user/:user-id/profile"}>
|
||||||
<Flex className="justify-between w-full gap-2">
|
<Flex className="justify-between gap-2">
|
||||||
<Icon>
|
<Icon>
|
||||||
<PersonIcon />
|
<PersonIcon />
|
||||||
</Icon>
|
</Icon>
|
||||||
|
|
||||||
<Text>{t("profile")}</Text>
|
<Text>{t("profile")}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</Link>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
<DropdownMenu.Item onClick={() => navigate("/user/blogs")}>
|
<DropdownMenu.Item>
|
||||||
<div>
|
<Link to={"/user/blogs"}>
|
||||||
<Flex className="justify-between gap-2">
|
<Flex className="justify-between gap-2">
|
||||||
<Icon>
|
<Icon>
|
||||||
<LaptopIcon />
|
<LaptopIcon />
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{t("yourBlogs")}</Text>
|
<Text>{t("yourBlogs")}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
</div>
|
</Link>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
|
|
||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
|
|
||||||
<DropdownMenu.Item color={user ? "red" : "green"}>
|
<DropdownMenu.Item color={user ? "red" : "green"}>
|
||||||
{user ? <LogoutButton /> : <LoginButton />}
|
{user ? (
|
||||||
|
<LogoutButton />
|
||||||
|
) : (
|
||||||
|
<LoginButton />
|
||||||
|
)}
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
</DropdownMenu.Root>
|
</DropdownMenu.Root>
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default function SearchField() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-center flex-1">
|
<div className="flex justify-center flex-1">
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
className="hidden w-2/3 rounded-lg"
|
className="w-2/3 rounded-lg"
|
||||||
placeholder={t("search")}
|
placeholder={t("search")}
|
||||||
>
|
>
|
||||||
<TextField.Slot>
|
<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 { useQuery } from "@tanstack/react-query";
|
||||||
import { lazy, Suspense } from "react";
|
import { Link } from "react-router-dom";
|
||||||
import { axiosLocalhost } from "../../api/axios/axios";
|
import { axiosLocalhost } from "../../api/axios/axios";
|
||||||
|
|
||||||
type TUserNicknameLink = {
|
type TUserNicknameLink = {
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserCard = lazy(() => import("../UserCard/UserCard"));
|
|
||||||
|
|
||||||
export default function UserNicknameLink(props: TUserNicknameLink) {
|
export default function UserNicknameLink(props: TUserNicknameLink) {
|
||||||
const { data, isPending } = useQuery({
|
const { data, isPending } = useQuery({
|
||||||
queryKey: [`userLink${props.userId}`],
|
queryKey: [`userLink${props.userId}`],
|
||||||
@ -23,38 +21,13 @@ export default function UserNicknameLink(props: TUserNicknameLink) {
|
|||||||
if (isPending)
|
if (isPending)
|
||||||
return (
|
return (
|
||||||
<Skeleton>
|
<Skeleton>
|
||||||
<Text
|
<Text>@Nickname</Text>
|
||||||
size={{
|
|
||||||
sm: "4",
|
|
||||||
md: "5",
|
|
||||||
lg: "6",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
@Nickname
|
|
||||||
</Text>
|
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard.Root>
|
<Link to={`/users/${data}`}>
|
||||||
<HoverCard.Trigger>
|
<Text>@{data}</Text>
|
||||||
<Link href={`/users/${data}`}>
|
|
||||||
<Text
|
|
||||||
size={{
|
|
||||||
sm: "3",
|
|
||||||
md: "4",
|
|
||||||
lg: "5",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
@{data}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
</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 { axiosLocalhost } from "../../../api/axios/axios";
|
||||||
import { userAtom } from "../../../AtomStore/AtomStore";
|
import { userAtom } from "../../../AtomStore/AtomStore";
|
||||||
import UseCapsLock from "../../../hooks/useCapsLock";
|
import UseCapsLock from "../../../hooks/useCapsLock";
|
||||||
import { JSONWithInt64 } from "../../../utils/idnex";
|
|
||||||
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
||||||
|
|
||||||
type TLoginData = {
|
type TLoginData = {
|
||||||
@ -29,18 +28,12 @@ export default function LoginPage() {
|
|||||||
mutationFn: async (data: TLoginData) => {
|
mutationFn: async (data: TLoginData) => {
|
||||||
let response = await axiosLocalhost.post(
|
let response = await axiosLocalhost.post(
|
||||||
"/login",
|
"/login",
|
||||||
JSON.stringify(data),
|
JSON.stringify(data)
|
||||||
{
|
|
||||||
transformResponse: [data => data]
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsedData = JSONWithInt64(response.data);
|
|
||||||
|
|
||||||
setUserAtom({
|
setUserAtom({
|
||||||
username: parsedData.username,
|
username: response.data.username,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
id: parsedData.id,
|
id: response.data.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -70,7 +63,7 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
size={"2"}
|
size={"2"}
|
||||||
className="absolute w-[25rem] min-w-[20rem]
|
className="absolute w-1/4
|
||||||
left-[50%] top-[50%]
|
left-[50%] top-[50%]
|
||||||
translate-x-[-50%] translate-y-[-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 { useQuery } from "@tanstack/react-query";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { axiosLocalhost } from "../../api/axios/axios";
|
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||||
import TrueEditor from "../../Components/Editor/TrueEditor";
|
import Editor from "../../../Components/Editor/Editor";
|
||||||
import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton";
|
import SubmitChangesButton from "./SubmitChangesButton/SubmitChangesButton";
|
||||||
|
|
||||||
export default function PostRedactor() {
|
export default function PostRedactor() {
|
||||||
@ -12,7 +12,7 @@ export default function PostRedactor() {
|
|||||||
|
|
||||||
const queryParams = useParams();
|
const queryParams = useParams();
|
||||||
|
|
||||||
const { isLoading } = useQuery({
|
const { isPending } = useQuery({
|
||||||
queryKey: ["changePostKey", queryParams.postId],
|
queryKey: ["changePostKey", queryParams.postId],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
@ -31,20 +31,16 @@ export default function PostRedactor() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
gcTime: 0,
|
gcTime: 0,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box className="flex flex-col flex-1 overflow-hidden">
|
<Box className="flex flex-col flex-1">
|
||||||
<Flex
|
<Flex gap={"4"} direction={"column"} className="flex-[1]">
|
||||||
gap={"4"}
|
<Container className="flex-[1]">
|
||||||
direction={"column"}
|
|
||||||
className="overflow-hidden"
|
|
||||||
>
|
|
||||||
<Container className="">
|
|
||||||
<input
|
<input
|
||||||
disabled={isLoading}
|
disabled={isPending}
|
||||||
placeholder={"Post title"}
|
placeholder={"Post title"}
|
||||||
className="mb-2 border-0 border-b-[1px]
|
className="mb-2 border-0 border-b-[1px]
|
||||||
outline-none w-full border-b-gray-400
|
outline-none w-full border-b-gray-400
|
||||||
@ -56,7 +52,7 @@ export default function PostRedactor() {
|
|||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
{/* <Container className="overflow-hidden flex-grow-[100]">
|
<Container className="overflow-y-auto flex-grow-[100]">
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<Spinner />
|
<Spinner />
|
||||||
) : (
|
) : (
|
||||||
@ -65,23 +61,13 @@ export default function PostRedactor() {
|
|||||||
onChange={setContentValue}
|
onChange={setContentValue}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Container> */}
|
</Container>
|
||||||
|
|
||||||
{isLoading ? (
|
<Box className="flex justify-center flex-[1] mb-4">
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<TrueEditor
|
|
||||||
defaultValue={contentValue}
|
|
||||||
onChange={setContentValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box className="flex justify-center flex-[1] mb-4 ">
|
|
||||||
<SubmitChangesButton
|
<SubmitChangesButton
|
||||||
contentValue={contentValue}
|
contentValue={contentValue}
|
||||||
titleValue={titleValue}
|
titleValue={titleValue}
|
||||||
className="text-2xl rounded-full w-52"
|
className="text-2xl rounded-full w-52" />
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
@ -3,8 +3,8 @@ import { useMutation } from "@tanstack/react-query";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
import { axiosLocalhost } from "../../../../api/axios/axios";
|
||||||
import useToast from "../../../hooks/useToast";
|
import useToast from "../../../../hooks/useToast";
|
||||||
|
|
||||||
type TSubmitChangesButton = {
|
type TSubmitChangesButton = {
|
||||||
className: string;
|
className: string;
|
||||||
@ -9,7 +9,6 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import { axiosLocalhost } from "../../../api/axios/axios";
|
import { axiosLocalhost } from "../../../api/axios/axios";
|
||||||
import { userAtom } from "../../../AtomStore/AtomStore";
|
import { userAtom } from "../../../AtomStore/AtomStore";
|
||||||
import UseCapsLock from "../../../hooks/useCapsLock";
|
import UseCapsLock from "../../../hooks/useCapsLock";
|
||||||
import { JSONWithInt64 } from "../../../utils/idnex";
|
|
||||||
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
import ShowPasswordButton from "../ShowPasswordButton/ShowPasswordButton";
|
||||||
|
|
||||||
type TRegisterData = {
|
type TRegisterData = {
|
||||||
@ -32,16 +31,11 @@ export default function RegisterPage() {
|
|||||||
|
|
||||||
const registerMutation = useMutation({
|
const registerMutation = useMutation({
|
||||||
mutationFn: async (data: TRegisterData) => {
|
mutationFn: async (data: TRegisterData) => {
|
||||||
let response = await axiosLocalhost.post("/users", JSON.stringify(data), {
|
let response = await axiosLocalhost.post("/users", JSON.stringify(data));
|
||||||
transformResponse: [data => data]
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedResponse = JSONWithInt64(response.data)
|
|
||||||
|
|
||||||
setUserAtom({
|
setUserAtom({
|
||||||
username: parsedResponse.username,
|
username: response.data.username,
|
||||||
isAdmin: false,
|
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 { useAtom, useSetAtom } from "jotai";
|
||||||
import {
|
import {
|
||||||
postCreationAtom,
|
postCreationAtom,
|
||||||
postCreationTitleAtom,
|
postCreationTitleAtom
|
||||||
} from "../../AtomStore/AtomStore";
|
} from "../../AtomStore/AtomStore";
|
||||||
import TrueEditor from "../../Components/Editor/TrueEditor";
|
import Editor from "../../Components/Editor/Editor";
|
||||||
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
import SubmitPostButton from "./SubmitPostButton/SubmitPostButton";
|
||||||
|
|
||||||
export default function PostCreatorPage() {
|
export default function PostCreatorPage() {
|
||||||
@ -13,13 +13,10 @@ export default function PostCreatorPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box className="flex flex-col flex-1 overflow-hidden">
|
|
||||||
<Flex
|
<Box className="flex flex-col flex-1">
|
||||||
gap={"4"}
|
<Flex gap={"4"} direction={"column"} className="flex-[1]">
|
||||||
direction={"column"}
|
<Container className="flex-[1]">
|
||||||
className="justify-start overflow-hidden"
|
|
||||||
>
|
|
||||||
<Container>
|
|
||||||
<input
|
<input
|
||||||
placeholder={"Post title"}
|
placeholder={"Post title"}
|
||||||
className="mb-2 border-0 border-b-[1px]
|
className="mb-2 border-0 border-b-[1px]
|
||||||
@ -32,7 +29,9 @@ export default function PostCreatorPage() {
|
|||||||
/>
|
/>
|
||||||
</Container>
|
</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">
|
<Box className="flex justify-center flex-[1] mb-4">
|
||||||
<SubmitPostButton className="text-2xl rounded-full w-52" />
|
<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 { ImageIcon } from "@radix-ui/react-icons";
|
||||||
import { Box, Card, Flex, Heading, Text, Tooltip } from "@radix-ui/themes";
|
import { Box, Card, Heading } from "@radix-ui/themes";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import "dayjs/locale/ru";
|
|
||||||
import { Interweave } from "interweave";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Post } from "../../../@types/PostTypes";
|
import { GetRandomPostsRow } from "../../../@types/PostTypes";
|
||||||
import UserNicknameLink from "../../../Components/UserNicknameLink/UserNicknameLink";
|
|
||||||
import InsetImage from "./InsetImage/InsetImage";
|
|
||||||
|
|
||||||
|
type TPostCard = {
|
||||||
|
post: GetRandomPostsRow;
|
||||||
|
};
|
||||||
|
|
||||||
export default function PostCard(props: Post) {
|
export default function PostCard({ post }: TPostCard) {
|
||||||
const navigate = useNavigate();
|
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");
|
|
||||||
|
|
||||||
const clickHandler = () => {
|
const clickHandler = () => {
|
||||||
navigate(`/posts/${props.post_id.toString()}`);
|
navigate(`/posts/${post.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);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className="h-32 mb-4" onClick={clickHandler}>
|
||||||
ref={ref}
|
<Box className="flex size-full">
|
||||||
className="flex w-full cursor-pointer max-h-72"
|
<Box>
|
||||||
onClick={clickHandler}
|
<ImageIcon className="w-full h-full" />
|
||||||
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>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Flex className="gap-2 lg:gap-4">
|
<Box className="px-4 pt-2">
|
||||||
<Tooltip content={`Written at`}>
|
<Heading>{post.title}</Heading>
|
||||||
<Flex className="items-center gap-2 h-fit">
|
</Box>
|
||||||
<CalendarIcon className="size-6" />
|
</Box>
|
||||||
<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>
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,80 +1,67 @@
|
|||||||
import { Box, Flex, ScrollArea, Text } from "@radix-ui/themes";
|
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
import { Container, Flex, Heading, Separator } from "@radix-ui/themes";
|
||||||
import { useEffect } from "react";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { GetRandomPostsRow } from "../../@types/PostTypes";
|
||||||
import { SelectedPostsResponse } from "../../@types/PostTypes";
|
|
||||||
import { axiosLocalhost } from "../../api/axios/axios";
|
import { axiosLocalhost } from "../../api/axios/axios";
|
||||||
import PostCard from "./PostCard/PostCard";
|
import PostCard from "./PostCard/PostCard";
|
||||||
|
|
||||||
const LIMIT = 7;
|
const LIMIT = 10;
|
||||||
|
|
||||||
export default function RandomPostsPage() {
|
export default function RandomPostsPage() {
|
||||||
const { t } = useTranslation();
|
const {t} = useTranslation()
|
||||||
|
|
||||||
const [ref, inView] = useInView();
|
const { data, refetch } = useQuery({
|
||||||
|
queryKey: ["random_posts_key"],
|
||||||
const { data, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
queryFn: async () => {
|
||||||
queryKey: [`random_post_inf`],
|
try {
|
||||||
queryFn: async ({ pageParam }): Promise<SelectedPostsResponse> => {
|
|
||||||
const response = await axiosLocalhost.get(
|
const response = await axiosLocalhost.get(
|
||||||
`/posts/random?limit=${LIMIT}&offset=${pageParam}`
|
`/posts/random?limit=${LIMIT}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.data as SelectedPostsResponse;
|
return response.data as GetRandomPostsRow[];
|
||||||
},
|
} catch (error) {
|
||||||
initialPageParam: 0,
|
console.log(`Something went wrong`);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}, [inView]);
|
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollArea>
|
<Flex direction={"column"} className="mx-auto">
|
||||||
<Flex
|
<Heading size={"9"} weight={"regular"} className="text-center">
|
||||||
direction={"column"}
|
{t("discover")}
|
||||||
className="w-full overflow-hidden sm:mx-auto max-w-pc-width "
|
</Heading>
|
||||||
>
|
|
||||||
<Flex direction={"column"} gap={"4"} className="mx-4 xl:mx-0">
|
<Separator size={"4"} className="my-8" />
|
||||||
{data?.pages.map((post, i) => {
|
|
||||||
|
<ScrollArea.Root className="w-full h-full overflow-hidden">
|
||||||
|
<ScrollArea.Viewport className="overflow-scroll rounded size-full">
|
||||||
|
{data?.map((post, i) => {
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Container size={"3"} key={`post${i}`}>
|
||||||
direction={"column"}
|
<PostCard post={post} />
|
||||||
gap={"4"}
|
</Container>
|
||||||
key={`${i}`}
|
|
||||||
>
|
|
||||||
{post.selected_posts.map((post, j) => {
|
|
||||||
return <PostCard key={j} {...post} />;
|
|
||||||
})}
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</ScrollArea.Viewport>
|
||||||
<Box ref={ref} className="w-full mb-4 text-center">
|
<ScrollArea.Scrollbar
|
||||||
{isFetching ? (
|
className="z-50 flex touch-none select-none p-0.5 w-2"
|
||||||
<Text>Loading more...</Text>
|
orientation="vertical"
|
||||||
) : hasNextPage ? (
|
|
||||||
<Text
|
|
||||||
className="cursor-pointer"
|
|
||||||
onClick={() => fetchNextPage()}
|
|
||||||
>
|
>
|
||||||
Load more posts
|
<ScrollArea.Thumb className="relative flex-1 rounded-[10px] bg-slate-200"/>
|
||||||
</Text>
|
</ScrollArea.Scrollbar>
|
||||||
) : (
|
{/* <ScrollArea.Scrollbar
|
||||||
<Text>No more posts to load</Text>
|
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"
|
||||||
</Box>
|
>
|
||||||
|
<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>
|
||||||
</Flex>
|
|
||||||
</ScrollArea>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import BlogCreationDialog from "../../Components/Dialogs/BlogCreationDialog/Blog
|
|||||||
import { JSONWithInt64 } from "../../utils/idnex";
|
import { JSONWithInt64 } from "../../utils/idnex";
|
||||||
import SkeletonBoxes from "./SkeletonBoxes/SkeletonBoxes";
|
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() {
|
export default function UserBlogsPage() {
|
||||||
const { data, isPending, isFetching } = useQuery({
|
const { data, isPending, isFetching } = useQuery({
|
||||||
queryKey: ["userBlogs"],
|
queryKey: ["userBlogs"],
|
||||||
@ -15,7 +19,7 @@ export default function UserBlogsPage() {
|
|||||||
transformResponse: [(data) => data],
|
transformResponse: [(data) => data],
|
||||||
});
|
});
|
||||||
|
|
||||||
const temp = JSONWithInt64(response.data);
|
let temp = JSONWithInt64(response.data);
|
||||||
|
|
||||||
return temp as any[];
|
return temp as any[];
|
||||||
},
|
},
|
||||||
@ -48,14 +52,16 @@ export default function UserBlogsPage() {
|
|||||||
<ScrollArea.Viewport className="size-full">
|
<ScrollArea.Viewport className="size-full">
|
||||||
<Flex direction={"column"} gap={"2"}>
|
<Flex direction={"column"} gap={"2"}>
|
||||||
{data
|
{data
|
||||||
? data?.map((blog: any, b: number) => {
|
? data?.map((blog: any, b) => {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<BlogBox
|
<BlogBox
|
||||||
key={b}
|
key={b}
|
||||||
title={blog.title}
|
title={blog.title}
|
||||||
blogId={blog.blog_id}
|
blogId={blog.blog_id}
|
||||||
userId={blog.user_id}
|
userId={blog.user_id}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: 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,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=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 base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@ -11,12 +10,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
|
||||||
--max-content-width: 70rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radix-themes {
|
.radix-themes {
|
||||||
--default-font-family:
|
--default-font-family: "Times New Roman"; ;
|
||||||
|
|
||||||
--heading-font-family: "Edu AU VIC WA NT Pre", cursive;
|
--heading-font-family: "Edu AU VIC WA NT Pre", cursive;
|
||||||
/* Your custom font for <Heading> components */
|
/* Your custom font for <Heading> components */
|
||||||
--code-font-family:
|
--code-font-family:
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Outlet } from "react-router-dom";
|
|||||||
import { axiosLocalhost } from "../../api/axios/axios";
|
import { axiosLocalhost } from "../../api/axios/axios";
|
||||||
import { userAtom } from "../../AtomStore/AtomStore";
|
import { userAtom } from "../../AtomStore/AtomStore";
|
||||||
import NavBar from "../../Components/NavBar/NavBar";
|
import NavBar from "../../Components/NavBar/NavBar";
|
||||||
import { JSONWithInt64 } from "../../utils/idnex";
|
|
||||||
|
|
||||||
const REFETCH_INTERVAL_IN_MINUTES = 5;
|
const REFETCH_INTERVAL_IN_MINUTES = 5;
|
||||||
const RETRY_INTERVAL_IN_SECONDS = 1;
|
const RETRY_INTERVAL_IN_SECONDS = 1;
|
||||||
@ -13,6 +12,10 @@ const RETRY_INTERVAL_IN_SECONDS = 1;
|
|||||||
const SECONDS_IN_MINUTE = 60;
|
const SECONDS_IN_MINUTE = 60;
|
||||||
const MILLS_IN_SECOND = 1000;
|
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() {
|
export default function MainPage() {
|
||||||
const setUserData = useSetAtom(userAtom);
|
const setUserData = useSetAtom(userAtom);
|
||||||
|
|
||||||
@ -20,16 +23,12 @@ export default function MainPage() {
|
|||||||
queryKey: ["authKey"],
|
queryKey: ["authKey"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axiosLocalhost.get("/auth/check", {
|
const response = await axiosLocalhost.get("/auth/check");
|
||||||
transformResponse: [data => data]
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedResponse = JSONWithInt64(response.data)
|
|
||||||
|
|
||||||
setUserData({
|
setUserData({
|
||||||
isAdmin: parsedResponse["is_admin"],
|
isAdmin: response.data["is_admin"],
|
||||||
username: parsedResponse["username"],
|
username: response.data["username"],
|
||||||
id: parsedResponse["id"],
|
id: response.data["id"],
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -63,11 +62,10 @@ export default function MainPage() {
|
|||||||
direction={"column"}
|
direction={"column"}
|
||||||
className="min-h-[100vh] max-h-[100vh] overflow-hidden"
|
className="min-h-[100vh] max-h-[100vh] overflow-hidden"
|
||||||
>
|
>
|
||||||
|
<Box flexGrow={"1"} className="flex-[1]">
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<Box
|
</Box>
|
||||||
flexGrow={"1"}
|
<Box flexGrow={"100"} className="flex overflow-hidden flex-">
|
||||||
className="flex flex-col overflow-hidden"
|
|
||||||
>
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</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";
|
} from "react-router-dom";
|
||||||
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
import ArticleViewer from "../Components/ArticleViewer/ArticleViewer";
|
||||||
import MainPage from "../layout/MainPage/MainPage";
|
import MainPage from "../layout/MainPage/MainPage";
|
||||||
import ProfilePage from "../layout/ProfilePage/ProfilePage";
|
|
||||||
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
import AuthPageWrapper from "../Pages/AuthPageWrapper/AuthPageWrapper";
|
||||||
import BlogPage from "../Pages/BlogPage/BlogPage";
|
import BlogPage from "../Pages/BlogPage/BlogPage";
|
||||||
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
import LoginPage from "../Pages/LoginRegisterPage/LoginPage/LoginPage";
|
||||||
|
import PostRedactor from "../Pages/LoginRegisterPage/PostRedactor/PostRedactor";
|
||||||
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
import RegisterPage from "../Pages/LoginRegisterPage/RegisterPage/RegisterPage";
|
||||||
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
|
import PostCreatorPage from "../Pages/PostCreatorPage/PostCreatorPage";
|
||||||
import PostRedactor from "../Pages/PostRedactor/PostRedactor";
|
|
||||||
import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
|
import RandomPostsPage from "../Pages/RandomPostsPage/RandomPostsPage";
|
||||||
import UserBlogsPage from "../Pages/UserBlogsPage/UserBlogsPage";
|
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() {
|
function ErrorBoundary() {
|
||||||
let error = useRouteError();
|
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="blogs/:blogId" element={<BlogPage />} />
|
||||||
|
|
||||||
<Route path="user" element={<Outlet />}>
|
<Route path="user" element={<Outlet />}>
|
||||||
|
|||||||
@ -7,9 +7,6 @@ export default {
|
|||||||
"primary-color": "var(--primary-color)",
|
"primary-color": "var(--primary-color)",
|
||||||
"secondary-color": "var(--secondary-color)",
|
"secondary-color": "var(--secondary-color)",
|
||||||
},
|
},
|
||||||
maxWidth: {
|
|
||||||
"pc-width": "var(--max-content-width)",
|
|
||||||
},
|
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
'times': "Times New Roman"
|
'times': "Times New Roman"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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.28.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.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: blogs_queries.sql
|
// source: blogs_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: bookmarks_queries.sql
|
// source: bookmarks_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: categories_queries.sql
|
// source: categories_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: comments_queries.sql
|
// source: comments_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: favorites_queries.sql
|
// source: favorites_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: likes_queries.sql
|
// source: likes_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,21 +1,13 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"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 {
|
type Blog struct {
|
||||||
BlogID int64 `json:"blog_id"`
|
BlogID int64 `json:"blog_id"`
|
||||||
UserID int64 `json:"user_id"`
|
UserID int64 `json:"user_id"`
|
||||||
@ -83,7 +75,6 @@ type Profile struct {
|
|||||||
Bio pgtype.Text `json:"bio"`
|
Bio pgtype.Text `json:"bio"`
|
||||||
AvatarUrl pgtype.Text `json:"avatar_url"`
|
AvatarUrl pgtype.Text `json:"avatar_url"`
|
||||||
WebsiteUrl pgtype.Text `json:"website_url"`
|
WebsiteUrl pgtype.Text `json:"website_url"`
|
||||||
EmailVerified pgtype.Bool `json:"email_verified"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
@ -98,10 +89,4 @@ type User struct {
|
|||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
CreatedAt pgtype.Timestamp `json:"created_at"`
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
IsAdmin bool `json:"is_admin"`
|
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.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: multi_queries.sql
|
// source: multi_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: post_tags_queries.sql
|
// source: post_tags_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: post_votes_queries.sql
|
// source: post_votes_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: posts_queries.sql
|
// source: posts_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
@ -146,70 +146,45 @@ func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, e
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRandomPosts = `-- name: GetRandomPosts :one
|
const getRandomPosts = `-- name: GetRandomPosts :many
|
||||||
WITH all_posts AS (
|
SELECT post_id, blog_id, user_id, title, created_at
|
||||||
SELECT
|
FROM public.posts
|
||||||
COUNT(*) AS post_count
|
ORDER BY RANDOM()
|
||||||
FROM
|
LIMIT $1
|
||||||
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
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetRandomPostsParams struct {
|
|
||||||
Column1 interface{} `json:"column_1"`
|
|
||||||
Column2 interface{} `json:"column_2"`
|
|
||||||
Offset int32 `json:"offset"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetRandomPostsRow struct {
|
type GetRandomPostsRow struct {
|
||||||
SelectedPosts interface{} `json:"selected_posts"`
|
PostID int64 `json:"post_id"`
|
||||||
HasNextPage bool `json:"has_next_page"`
|
BlogID pgtype.Int8 `json:"blog_id"`
|
||||||
NextPageIndex int32 `json:"next_page_index"`
|
UserID int64 `json:"user_id"`
|
||||||
PrevPageIndex int32 `json:"prev_page_index"`
|
Title pgtype.Text `json:"title"`
|
||||||
|
CreatedAt pgtype.Timestamp `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetRandomPosts(ctx context.Context, arg GetRandomPostsParams) (GetRandomPostsRow, error) {
|
func (q *Queries) GetRandomPosts(ctx context.Context, limit int32) ([]GetRandomPostsRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getRandomPosts, arg.Column1, arg.Column2, arg.Offset)
|
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
|
var i GetRandomPostsRow
|
||||||
err := row.Scan(
|
if err := rows.Scan(
|
||||||
&i.SelectedPosts,
|
&i.PostID,
|
||||||
&i.HasNextPage,
|
&i.BlogID,
|
||||||
&i.NextPageIndex,
|
&i.UserID,
|
||||||
&i.PrevPageIndex,
|
&i.Title,
|
||||||
)
|
&i.CreatedAt,
|
||||||
return i, err
|
); 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
|
const updatePostBlogId = `-- name: UpdatePostBlogId :exec
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: profiles_queries.sql
|
// source: profiles_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
@ -15,7 +15,7 @@ const clearProfileByUserId = `-- name: ClearProfileByUserId :one
|
|||||||
UPDATE public.profiles
|
UPDATE public.profiles
|
||||||
SET bio='', avatar_url='', website_url=''
|
SET bio='', avatar_url='', website_url=''
|
||||||
WHERE user_id=$1
|
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) {
|
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.Bio,
|
||||||
&i.AvatarUrl,
|
&i.AvatarUrl,
|
||||||
&i.WebsiteUrl,
|
&i.WebsiteUrl,
|
||||||
&i.EmailVerified,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -35,7 +34,7 @@ const createProfileForUser = `-- name: CreateProfileForUser :one
|
|||||||
INSERT INTO public.profiles
|
INSERT INTO public.profiles
|
||||||
(user_id, bio, avatar_url, website_url)
|
(user_id, bio, avatar_url, website_url)
|
||||||
VALUES($1, '', '', '')
|
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) {
|
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.Bio,
|
||||||
&i.AvatarUrl,
|
&i.AvatarUrl,
|
||||||
&i.WebsiteUrl,
|
&i.WebsiteUrl,
|
||||||
&i.EmailVerified,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -62,7 +60,7 @@ func (q *Queries) DeleteProfileByUserId(ctx context.Context, userID int64) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getProfileByUserId = `-- name: GetProfileByUserId :one
|
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) {
|
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.Bio,
|
||||||
&i.AvatarUrl,
|
&i.AvatarUrl,
|
||||||
&i.WebsiteUrl,
|
&i.WebsiteUrl,
|
||||||
&i.EmailVerified,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -82,7 +79,7 @@ const updateProfileByUserId = `-- name: UpdateProfileByUserId :one
|
|||||||
UPDATE public.profiles
|
UPDATE public.profiles
|
||||||
SET bio=$2, avatar_url=$3, website_url=$4
|
SET bio=$2, avatar_url=$3, website_url=$4
|
||||||
WHERE user_id=$1
|
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 {
|
type UpdateProfileByUserIdParams struct {
|
||||||
@ -105,7 +102,6 @@ func (q *Queries) UpdateProfileByUserId(ctx context.Context, arg UpdateProfileBy
|
|||||||
&i.Bio,
|
&i.Bio,
|
||||||
&i.AvatarUrl,
|
&i.AvatarUrl,
|
||||||
&i.WebsiteUrl,
|
&i.WebsiteUrl,
|
||||||
&i.EmailVerified,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: tags_queries.sql
|
// source: tags_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.28.0
|
// sqlc v1.27.0
|
||||||
// source: users_queries.sql
|
// source: users_queries.sql
|
||||||
|
|
||||||
package db_repo
|
package db_repo
|
||||||
@ -13,7 +13,7 @@ const createUser = `-- name: CreateUser :one
|
|||||||
INSERT INTO public.users
|
INSERT INTO public.users
|
||||||
(user_id, username, email, "password", created_at, is_admin)
|
(user_id, username, email, "password", created_at, is_admin)
|
||||||
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP, false)
|
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 {
|
type CreateUserParams struct {
|
||||||
@ -38,7 +38,6 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
|||||||
&i.Password,
|
&i.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -64,7 +63,7 @@ func (q *Queries) DeleteUserByUsername(ctx context.Context, username string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllUsers = `-- name: GetAllUsers :many
|
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) {
|
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.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -96,7 +94,7 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getUserByEmailOrNickname = `-- name: GetUserByEmailOrNickname :one
|
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 {
|
type GetUserByEmailOrNicknameParams struct {
|
||||||
@ -114,13 +112,12 @@ func (q *Queries) GetUserByEmailOrNickname(ctx context.Context, arg GetUserByEma
|
|||||||
&i.Password,
|
&i.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUserById = `-- name: GetUserById :one
|
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) {
|
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.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUserByUsername = `-- name: GetUserByUsername :one
|
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) {
|
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.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -172,7 +167,7 @@ const updateUserPasswordHash = `-- name: UpdateUserPasswordHash :one
|
|||||||
UPDATE public.users
|
UPDATE public.users
|
||||||
SET "password"=$1
|
SET "password"=$1
|
||||||
WHERE user_id=$2
|
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 {
|
type UpdateUserPasswordHashParams struct {
|
||||||
@ -190,7 +185,6 @@ func (q *Queries) UpdateUserPasswordHash(ctx context.Context, arg UpdateUserPass
|
|||||||
&i.Password,
|
&i.Password,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.IsAdmin,
|
&i.IsAdmin,
|
||||||
&i.DisplayName,
|
|
||||||
)
|
)
|
||||||
return i, err
|
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 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 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;
|
|
||||||
@ -35,42 +35,8 @@ SET blog_id=$2, updated_at=CURRENT_TIMESTAMP
|
|||||||
WHERE post_id = $1
|
WHERE post_id = $1
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: GetRandomPosts :one
|
-- name: GetRandomPosts :many
|
||||||
WITH all_posts AS (
|
SELECT post_id, blog_id, user_id, title, created_at
|
||||||
SELECT
|
FROM public.posts
|
||||||
COUNT(*) AS post_count
|
ORDER BY RANDOM()
|
||||||
FROM
|
LIMIT $1;
|
||||||
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;
|
|
||||||
@ -22,7 +22,7 @@ func TargetMiddleware() gin.HandlerFunc {
|
|||||||
case "POST":
|
case "POST":
|
||||||
c.Set("target", POST)
|
c.Set("target", POST)
|
||||||
case "GET":
|
case "GET":
|
||||||
c.Set("target", GET)
|
c.Set("target", DELETE)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@ -16,7 +16,7 @@ func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
|
|||||||
claims, exists := c.Get(global.ContextTokenData)
|
claims, exists := c.Get(global.ContextTokenData)
|
||||||
|
|
||||||
if !exists {
|
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(
|
parsedUserId, err := strconv.ParseInt(
|
||||||
|
|||||||
@ -12,7 +12,7 @@ func GetUserIdFromContext(c *gin.Context) (int64, error) {
|
|||||||
userId, exists := c.Get(global.ContextUserId)
|
userId, exists := c.Get(global.ContextUserId)
|
||||||
|
|
||||||
if !exists {
|
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 {
|
if parsedUserId, err := strconv.ParseInt(userId.(string), 10, 64); err != nil {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const (
|
|||||||
POST_BLOG_MIDDLEWARE = "POST_BLOG_MIDDLEWARE"
|
POST_BLOG_MIDDLEWARE = "POST_BLOG_MIDDLEWARE"
|
||||||
POST_VOTE_MIDDLEWARE = "POST_VOTE_MIDDLEWARE"
|
POST_VOTE_MIDDLEWARE = "POST_VOTE_MIDDLEWARE"
|
||||||
POST_VOTES_MIDDLEWARE = "POST_VOTES_MIDDLEWARE"
|
POST_VOTES_MIDDLEWARE = "POST_VOTES_MIDDLEWARE"
|
||||||
USER_MIDDLEWARE = "USER_MIDDLEWARE"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var MiddlewareProvider = middleware.MiddlewareProvider{
|
var MiddlewareProvider = middleware.MiddlewareProvider{
|
||||||
@ -153,25 +152,6 @@ var policiesToRegister = map[string]middleware.RulesToCheck{
|
|||||||
MustBeCompleted: rules.ALL_RULES_MUST_BE_COMPLETED,
|
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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitMiddlewareProvider() {
|
func InitMiddlewareProvider() {
|
||||||
|
|||||||
@ -19,38 +19,25 @@ func GetRandomPost(c *gin.Context) {
|
|||||||
return
|
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 :=
|
postsData, err :=
|
||||||
db_repo.New(db_connection.Dbx).
|
db_repo.New(db_connection.Dbx).
|
||||||
GetRandomPosts(context.Background(), params)
|
GetRandomPosts(context.Background(), int32(limit))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest_api_stuff.InternalErrorAnswer(c, err)
|
rest_api_stuff.InternalErrorAnswer(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// result := make([]any, 0)
|
result := make([]any, 0)
|
||||||
|
|
||||||
// for _, post := range postsData {
|
for _, post := range postsData {
|
||||||
// result = append(result, gin.H{
|
result = append(result, gin.H{
|
||||||
// "post_id": strconv.Itoa(int(post.PostID)),
|
"post_id": strconv.Itoa(int(post.PostID)),
|
||||||
// "title": post.Title,
|
"title": post.Title,
|
||||||
// "user_id": strconv.Itoa(int(post.UserID)),
|
"user_id": strconv.Itoa(int(post.UserID)),
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, postsData)
|
c.IndentedJSON(http.StatusOK, result)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -194,14 +194,6 @@ func SetupRotes(g *gin.Engine) error {
|
|||||||
voteroutes.GetVotes,
|
voteroutes.GetVotes,
|
||||||
)
|
)
|
||||||
|
|
||||||
userGroup := g.Group("/users/")
|
|
||||||
userGroup.Use(MiddlewareProvider.GetMiddleware(USER_MIDDLEWARE))
|
|
||||||
|
|
||||||
userGroup.GET(
|
|
||||||
"/info/:user-id",
|
|
||||||
userroutes.GetUserInfo,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Admin group routes
|
// Admin group routes
|
||||||
adminGroup := g.Group("/admin/")
|
adminGroup := g.Group("/admin/")
|
||||||
adminGroup.Use(middleware.AdminMiddleware())
|
adminGroup.Use(middleware.AdminMiddleware())
|
||||||
|
|||||||
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user