Merge pull request #1 from Nekiiinkognito/init

Init
This commit is contained in:
Maxim 2024-11-09 22:52:06 +03:00 committed by GitHub
commit c74b71163a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
88 changed files with 11644 additions and 1 deletions

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
secret.env
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"godotenv"
]
}

View File

@ -1 +1,3 @@
# Enshi
This (probably) will be good blog-platform.

4
enshi/.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": false
}

50
enshi/README.md Normal file
View File

@ -0,0 +1,50 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
```js
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```

28
enshi/eslint.config.js Normal file
View File

@ -0,0 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

21
enshi/index.html Normal file
View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Enshi</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

6974
enshi/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

49
enshi/package.json Normal file
View File

@ -0,0 +1,49 @@
{
"name": "enshi",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-navigation-menu": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@radix-ui/themes": "^3.1.3",
"@tanstack/react-query": "^5.55.0",
"html-react-parser": "^5.1.16",
"i18n": "^0.15.1",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"jotai": "^2.9.3",
"primereact": "^10.8.2",
"quill": "^2.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-i18next": "^15.0.1",
"react-quill": "^2.0.0",
"react-router-dom": "^6.26.2"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.45",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.3",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
}

6
enshi/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

1
enshi/public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

966
enshi/src/App.css Normal file
View File

@ -0,0 +1,966 @@
.controls {
display: flex;
border: 1px solid #ccc;
border-top: 0;
padding: 10px;
}
.controls-right {
margin-left: auto;
}
.state {
margin: 10px 0;
font-family: monospace;
}
.state-title {
color: #999;
text-transform: uppercase;
}
/*!
* Quill Editor v1.3.6
* https://quilljs.com/
* Copyright (c) 2014, Jason Chen
* Copyright (c) 2013, salesforce.com
*/
.ql-container {
box-sizing: border-box;
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
height: 100%;
margin: 0px;
position: relative;
}
.ql-container.ql-disabled .ql-tooltip {
visibility: hidden;
}
.ql-container.ql-disabled .ql-editor ul[data-checked] > li::before {
pointer-events: none;
}
.ql-clipboard {
left: -100000px;
height: 1px;
overflow-y: hidden;
position: absolute;
top: 50%;
}
.ql-clipboard p {
margin: 0;
padding: 0;
}
.ql-editor {
box-sizing: border-box;
line-height: 1.42;
height: 100%;
outline: none;
overflow-y: auto;
padding: 12px 15px;
tab-size: 4;
-moz-tab-size: 4;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
}
.ql-editor > * {
cursor: text;
}
.ql-editor p,
.ql-editor ol,
.ql-editor ul,
.ql-editor pre,
.ql-editor blockquote,
.ql-editor h1,
.ql-editor h2,
.ql-editor h3,
.ql-editor h4,
.ql-editor h5,
.ql-editor h6 {
margin: 0;
padding: 0;
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol,
.ql-editor ul {
padding-left: 1.5em;
}
.ql-editor ol > li,
.ql-editor ul > li {
list-style-type: none;
}
.ql-editor ul > li::before {
content: '\2022';
}
.ql-editor ul[data-checked=true],
.ql-editor ul[data-checked=false] {
pointer-events: none;
}
.ql-editor ul[data-checked=true] > li *,
.ql-editor ul[data-checked=false] > li * {
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before,
.ql-editor ul[data-checked=false] > li::before {
color: #777;
cursor: pointer;
pointer-events: all;
}
.ql-editor ul[data-checked=true] > li::before {
content: '\2611';
}
.ql-editor ul[data-checked=false] > li::before {
content: '\2610';
}
.ql-editor li::before {
display: inline-block;
white-space: nowrap;
width: 1.2em;
}
.ql-editor li:not(.ql-direction-rtl)::before {
margin-left: -1.5em;
margin-right: 0.3em;
text-align: right;
}
.ql-editor li.ql-direction-rtl::before {
margin-left: 0.3em;
margin-right: -1.5em;
}
.ql-editor ol li:not(.ql-direction-rtl),
.ql-editor ul li:not(.ql-direction-rtl) {
padding-left: 1.5em;
}
.ql-editor ol li.ql-direction-rtl,
.ql-editor ul li.ql-direction-rtl {
padding-right: 1.5em;
}
.ql-editor ol li {
counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
counter-increment: list-0;
}
.ql-editor ol li:before {
content: counter(list-0, decimal) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-increment: list-1;
}
.ql-editor ol li.ql-indent-1:before {
content: counter(list-1, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-1 {
counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-2 {
counter-increment: list-2;
}
.ql-editor ol li.ql-indent-2:before {
content: counter(list-2, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-2 {
counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-3 {
counter-increment: list-3;
}
.ql-editor ol li.ql-indent-3:before {
content: counter(list-3, decimal) '. ';
}
.ql-editor ol li.ql-indent-3 {
counter-reset: list-4 list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-4 {
counter-increment: list-4;
}
.ql-editor ol li.ql-indent-4:before {
content: counter(list-4, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-4 {
counter-reset: list-5 list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-5 {
counter-increment: list-5;
}
.ql-editor ol li.ql-indent-5:before {
content: counter(list-5, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-5 {
counter-reset: list-6 list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-6 {
counter-increment: list-6;
}
.ql-editor ol li.ql-indent-6:before {
content: counter(list-6, decimal) '. ';
}
.ql-editor ol li.ql-indent-6 {
counter-reset: list-7 list-8 list-9;
}
.ql-editor ol li.ql-indent-7 {
counter-increment: list-7;
}
.ql-editor ol li.ql-indent-7:before {
content: counter(list-7, lower-alpha) '. ';
}
.ql-editor ol li.ql-indent-7 {
counter-reset: list-8 list-9;
}
.ql-editor ol li.ql-indent-8 {
counter-increment: list-8;
}
.ql-editor ol li.ql-indent-8:before {
content: counter(list-8, lower-roman) '. ';
}
.ql-editor ol li.ql-indent-8 {
counter-reset: list-9;
}
.ql-editor ol li.ql-indent-9 {
counter-increment: list-9;
}
.ql-editor ol li.ql-indent-9:before {
content: counter(list-9, decimal) '. ';
}
.ql-editor .ql-indent-1:not(.ql-direction-rtl) {
padding-left: 3em;
}
.ql-editor li.ql-indent-1:not(.ql-direction-rtl) {
padding-left: 4.5em;
}
.ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 3em;
}
.ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right {
padding-right: 4.5em;
}
.ql-editor .ql-indent-2:not(.ql-direction-rtl) {
padding-left: 6em;
}
.ql-editor li.ql-indent-2:not(.ql-direction-rtl) {
padding-left: 7.5em;
}
.ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 6em;
}
.ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right {
padding-right: 7.5em;
}
.ql-editor .ql-indent-3:not(.ql-direction-rtl) {
padding-left: 9em;
}
.ql-editor li.ql-indent-3:not(.ql-direction-rtl) {
padding-left: 10.5em;
}
.ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 9em;
}
.ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right {
padding-right: 10.5em;
}
.ql-editor .ql-indent-4:not(.ql-direction-rtl) {
padding-left: 12em;
}
.ql-editor li.ql-indent-4:not(.ql-direction-rtl) {
padding-left: 13.5em;
}
.ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 12em;
}
.ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right {
padding-right: 13.5em;
}
.ql-editor .ql-indent-5:not(.ql-direction-rtl) {
padding-left: 15em;
}
.ql-editor li.ql-indent-5:not(.ql-direction-rtl) {
padding-left: 16.5em;
}
.ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 15em;
}
.ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right {
padding-right: 16.5em;
}
.ql-editor .ql-indent-6:not(.ql-direction-rtl) {
padding-left: 18em;
}
.ql-editor li.ql-indent-6:not(.ql-direction-rtl) {
padding-left: 19.5em;
}
.ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 18em;
}
.ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right {
padding-right: 19.5em;
}
.ql-editor .ql-indent-7:not(.ql-direction-rtl) {
padding-left: 21em;
}
.ql-editor li.ql-indent-7:not(.ql-direction-rtl) {
padding-left: 22.5em;
}
.ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 21em;
}
.ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right {
padding-right: 22.5em;
}
.ql-editor .ql-indent-8:not(.ql-direction-rtl) {
padding-left: 24em;
}
.ql-editor li.ql-indent-8:not(.ql-direction-rtl) {
padding-left: 25.5em;
}
.ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 24em;
}
.ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right {
padding-right: 25.5em;
}
.ql-editor .ql-indent-9:not(.ql-direction-rtl) {
padding-left: 27em;
}
.ql-editor li.ql-indent-9:not(.ql-direction-rtl) {
padding-left: 28.5em;
}
.ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 27em;
}
.ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right {
padding-right: 28.5em;
}
.ql-editor .ql-video {
display: block;
max-width: 100%;
}
.ql-editor .ql-video.ql-align-center {
margin: 0 auto;
}
.ql-editor .ql-video.ql-align-right {
margin: 0 0 0 auto;
}
.ql-editor .ql-bg-black {
background-color: #000;
}
.ql-editor .ql-bg-red {
background-color: #e60000;
}
.ql-editor .ql-bg-orange {
background-color: #f90;
}
.ql-editor .ql-bg-yellow {
background-color: #ff0;
}
.ql-editor .ql-bg-green {
background-color: #008a00;
}
.ql-editor .ql-bg-blue {
background-color: #06c;
}
.ql-editor .ql-bg-purple {
background-color: #93f;
}
.ql-editor .ql-color-white {
color: #fff;
}
.ql-editor .ql-color-red {
color: #e60000;
}
.ql-editor .ql-color-orange {
color: #f90;
}
.ql-editor .ql-color-yellow {
color: #ff0;
}
.ql-editor .ql-color-green {
color: #008a00;
}
.ql-editor .ql-color-blue {
color: #06c;
}
.ql-editor .ql-color-purple {
color: #93f;
}
.ql-editor .ql-font-serif {
font-family: Georgia, Times New Roman, serif;
}
.ql-editor .ql-font-monospace {
font-family: Monaco, Courier New, monospace;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-direction-rtl {
direction: rtl;
text-align: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor.ql-blank::before {
color: rgba(0,0,0,0.6);
content: attr(data-placeholder);
font-style: italic;
left: 15px;
pointer-events: none;
position: absolute;
right: 15px;
}
.ql-snow.ql-toolbar:after,
.ql-snow .ql-toolbar:after {
clear: both;
content: '';
display: table;
}
.ql-snow.ql-toolbar button,
.ql-snow .ql-toolbar button {
background: none;
border: none;
cursor: pointer;
display: inline-block;
float: left;
height: 24px;
padding: 3px 5px;
width: 28px;
}
.ql-snow.ql-toolbar button svg,
.ql-snow .ql-toolbar button svg {
float: left;
height: 100%;
}
.ql-snow.ql-toolbar button:active:hover,
.ql-snow .ql-toolbar button:active:hover {
outline: none;
}
.ql-snow.ql-toolbar input.ql-image[type=file],
.ql-snow .ql-toolbar input.ql-image[type=file] {
display: none;
}
.ql-snow.ql-toolbar button:hover,
.ql-snow .ql-toolbar button:hover,
.ql-snow.ql-toolbar button:focus,
.ql-snow .ql-toolbar button:focus,
.ql-snow.ql-toolbar button.ql-active,
.ql-snow .ql-toolbar button.ql-active,
.ql-snow.ql-toolbar .ql-picker-label:hover,
.ql-snow .ql-toolbar .ql-picker-label:hover,
.ql-snow.ql-toolbar .ql-picker-label.ql-active,
.ql-snow .ql-toolbar .ql-picker-label.ql-active,
.ql-snow.ql-toolbar .ql-picker-item:hover,
.ql-snow .ql-toolbar .ql-picker-item:hover,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected {
color: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-fill,
.ql-snow .ql-toolbar button:hover .ql-fill,
.ql-snow.ql-toolbar button:focus .ql-fill,
.ql-snow .ql-toolbar button:focus .ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-fill,
.ql-snow.ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:focus .ql-stroke.ql-fill,
.ql-snow.ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke.ql-fill,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke.ql-fill {
fill: #06c;
}
.ql-snow.ql-toolbar button:hover .ql-stroke,
.ql-snow .ql-toolbar button:hover .ql-stroke,
.ql-snow.ql-toolbar button:focus .ql-stroke,
.ql-snow .ql-toolbar button:focus .ql-stroke,
.ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow .ql-toolbar button.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke,
.ql-snow.ql-toolbar button:hover .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover .ql-stroke-miter,
.ql-snow.ql-toolbar button:focus .ql-stroke-miter,
.ql-snow .ql-toolbar button:focus .ql-stroke-miter,
.ql-snow.ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar button.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-label.ql-active .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item:hover .ql-stroke-miter,
.ql-snow.ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter,
.ql-snow .ql-toolbar .ql-picker-item.ql-selected .ql-stroke-miter {
stroke: #06c;
}
@media (pointer: coarse) {
.ql-snow.ql-toolbar button:hover:not(.ql-active),
.ql-snow .ql-toolbar button:hover:not(.ql-active) {
color: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-fill,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke,
.ql-snow.ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter,
.ql-snow .ql-toolbar button:hover:not(.ql-active) .ql-stroke-miter {
stroke: #444;
}
}
.ql-snow {
box-sizing: border-box;
}
.ql-snow * {
box-sizing: border-box;
}
.ql-snow .ql-hidden {
display: none;
}
.ql-snow .ql-out-bottom,
.ql-snow .ql-out-top {
visibility: hidden;
}
.ql-snow .ql-tooltip {
position: absolute;
transform: translateY(10px);
}
.ql-snow .ql-tooltip a {
cursor: pointer;
text-decoration: none;
}
.ql-snow .ql-tooltip.ql-flip {
transform: translateY(-10px);
}
.ql-snow .ql-formats {
display: inline-block;
vertical-align: middle;
}
.ql-snow .ql-formats:after {
clear: both;
content: '';
display: table;
}
.ql-snow .ql-stroke {
fill: none;
stroke: #444;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
.ql-snow .ql-stroke-miter {
fill: none;
stroke: #444;
stroke-miterlimit: 10;
stroke-width: 2;
}
.ql-snow .ql-fill,
.ql-snow .ql-stroke.ql-fill {
fill: #444;
}
.ql-snow .ql-empty {
fill: none;
}
.ql-snow .ql-even {
fill-rule: evenodd;
}
.ql-snow .ql-thin,
.ql-snow .ql-stroke.ql-thin {
stroke-width: 1;
}
.ql-snow .ql-transparent {
opacity: 0.4;
}
.ql-snow .ql-direction svg:last-child {
display: none;
}
.ql-snow .ql-direction.ql-active svg:last-child {
display: inline;
}
.ql-snow .ql-direction.ql-active svg:first-child {
display: none;
}
.ql-snow .ql-editor h1 {
font-size: 2em;
}
.ql-snow .ql-editor h2 {
font-size: 1.5em;
}
.ql-snow .ql-editor h3 {
font-size: 1.17em;
}
.ql-snow .ql-editor h4 {
font-size: 1em;
}
.ql-snow .ql-editor h5 {
font-size: 0.83em;
}
.ql-snow .ql-editor h6 {
font-size: 0.67em;
}
.ql-snow .ql-editor a {
text-decoration: underline;
}
.ql-snow .ql-editor blockquote {
border-left: 4px solid #ccc;
margin-bottom: 0px;
margin-top: 0px;
padding-left: 16px;
}
.ql-snow .ql-editor code,
.ql-snow .ql-editor pre {
background-color: #f0f0f0;
border-radius: 3px;
}
.ql-snow .ql-editor pre {
white-space: pre-wrap;
margin-bottom: 5px;
margin-top: 5px;
padding: 5px 10px;
}
.ql-snow .ql-editor code {
font-size: 85%;
padding: 2px 4px;
}
.ql-snow .ql-editor pre.ql-syntax {
background-color: #23241f;
color: #f8f8f2;
overflow: visible;
}
.ql-snow .ql-editor img {
max-width: 100%;
}
.ql-snow .ql-picker {
color: #444;
display: inline-block;
float: left;
font-size: 14px;
font-weight: 500;
height: 24px;
position: relative;
vertical-align: middle;
}
.ql-snow .ql-picker-label {
cursor: pointer;
display: inline-block;
height: 100%;
padding-left: 8px;
padding-right: 2px;
position: relative;
width: 100%;
}
.ql-snow .ql-picker-label::before {
display: inline-block;
line-height: 22px;
}
.ql-snow .ql-picker-options {
background-color: #fff;
display: none;
min-width: 100%;
padding: 4px 8px;
position: absolute;
white-space: nowrap;
}
.ql-snow .ql-picker-options .ql-picker-item {
cursor: pointer;
display: block;
padding-bottom: 5px;
padding-top: 5px;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label {
color: #ccc;
z-index: 2;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-fill {
fill: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-label .ql-stroke {
stroke: #ccc;
}
.ql-snow .ql-picker.ql-expanded .ql-picker-options {
display: block;
margin-top: -1px;
top: 100%;
z-index: 1;
}
.ql-snow .ql-color-picker,
.ql-snow .ql-icon-picker {
width: 28px;
}
.ql-snow .ql-color-picker .ql-picker-label,
.ql-snow .ql-icon-picker .ql-picker-label {
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-label svg,
.ql-snow .ql-icon-picker .ql-picker-label svg {
right: 4px;
}
.ql-snow .ql-icon-picker .ql-picker-options {
padding: 4px 0px;
}
.ql-snow .ql-icon-picker .ql-picker-item {
height: 24px;
width: 24px;
padding: 2px 4px;
}
.ql-snow .ql-color-picker .ql-picker-options {
padding: 3px 5px;
width: 152px;
}
.ql-snow .ql-color-picker .ql-picker-item {
border: 1px solid transparent;
float: left;
height: 16px;
margin: 2px;
padding: 0px;
width: 16px;
}
.ql-snow .ql-picker:not(.ql-color-picker):not(.ql-icon-picker) svg {
position: absolute;
margin-top: -9px;
right: 0;
top: 50%;
width: 18px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-label[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-label]:not([data-label=''])::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-label]:not([data-label=''])::before {
content: attr(data-label);
}
.ql-snow .ql-picker.ql-header {
width: 98px;
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: 'Heading 1';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: 'Heading 2';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: 'Heading 3';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: 'Heading 4';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: 'Heading 5';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: 'Heading 6';
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
font-size: 2em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
font-size: 1.5em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
font-size: 1.17em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
font-size: 1em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
font-size: 0.83em;
}
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
font-size: 0.67em;
}
.ql-snow .ql-picker.ql-font {
width: 108px;
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: 'Sans Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=serif]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
content: 'Serif';
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=monospace]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
content: 'Monospace';
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=serif]::before {
font-family: Georgia, Times New Roman, serif;
}
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=monospace]::before {
font-family: Monaco, Courier New, monospace;
}
.ql-snow .ql-picker.ql-size {
width: 98px;
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: 'Normal';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=small]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
content: 'Small';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=large]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
content: 'Large';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value=huge]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
content: 'Huge';
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=small]::before {
font-size: 10px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=large]::before {
font-size: 18px;
}
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value=huge]::before {
font-size: 32px;
}
.ql-snow .ql-color-picker.ql-background .ql-picker-item {
background-color: #fff;
}
.ql-snow .ql-color-picker.ql-color .ql-picker-item {
background-color: #000;
}
.ql-toolbar.ql-snow {
border: 1px solid #ccc;
box-sizing: border-box;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
padding: 8px;
}
.ql-toolbar.ql-snow .ql-formats {
margin-right: 15px;
}
.ql-toolbar.ql-snow .ql-picker-label {
border: 1px solid transparent;
}
.ql-toolbar.ql-snow .ql-picker-options {
border: 1px solid transparent;
box-shadow: rgba(0,0,0,0.2) 0 2px 8px;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-label {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-picker.ql-expanded .ql-picker-options {
border-color: #ccc;
}
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item.ql-selected,
.ql-toolbar.ql-snow .ql-color-picker .ql-picker-item:hover {
border-color: #000;
}
.ql-toolbar.ql-snow + .ql-container.ql-snow {
border-top: 0px;
}
.ql-snow .ql-tooltip {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0px 0px 5px #ddd;
color: #444;
padding: 5px 12px;
white-space: nowrap;
}
.ql-snow .ql-tooltip::before {
content: "Visit URL:";
line-height: 26px;
margin-right: 8px;
}
.ql-snow .ql-tooltip input[type=text] {
display: none;
border: 1px solid #ccc;
font-size: 13px;
height: 26px;
margin: 0px;
padding: 3px 5px;
width: 170px;
}
.ql-snow .ql-tooltip a.ql-preview {
display: inline-block;
max-width: 200px;
overflow-x: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.ql-snow .ql-tooltip a.ql-action::after {
border-right: 1px solid #ccc;
content: 'Edit';
margin-left: 16px;
padding-right: 8px;
}
.ql-snow .ql-tooltip a.ql-remove::before {
content: 'Remove';
margin-left: 8px;
}
.ql-snow .ql-tooltip a {
line-height: 26px;
}
.ql-snow .ql-tooltip.ql-editing a.ql-preview,
.ql-snow .ql-tooltip.ql-editing a.ql-remove {
display: none;
}
.ql-snow .ql-tooltip.ql-editing input[type=text] {
display: inline-block;
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0px;
content: 'Save';
padding-right: 0px;
}
.ql-snow .ql-tooltip[data-mode=link]::before {
content: "Enter link:";
}
.ql-snow .ql-tooltip[data-mode=formula]::before {
content: "Enter formula:";
}
.ql-snow .ql-tooltip[data-mode=video]::before {
content: "Enter video:";
}
.ql-snow a {
color: #06c;
}
.ql-container.ql-snow {
border: 1px solid #ccc;
}

42
enshi/src/App.tsx Normal file
View File

@ -0,0 +1,42 @@
import "./App.css";
import "@radix-ui/themes/styles.css";
import { Theme, ThemePanel } from "@radix-ui/themes";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { QueryClientProvider } from "@tanstack/react-query";
import queryClient from "./api/QueryClient/QueryClient";
import { routes } from "./routes/routes";
import { useEffect } from "react";
import "axios";
import { axiosLocalhost } from "./api/axios/axios";
const router = createBrowserRouter(routes);
export default function App() {
useEffect(() => {
let f = async () => {
let c = await axiosLocalhost.post(
"/login",
{
nickname: "StasikChess",
password: "123456",
}
);
console.log(c.headers);
console.log(document.cookie);
};
f();
}, []);
return (
<Theme className="h-fit" accentColor="indigo" grayColor="slate">
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ThemePanel />
</QueryClientProvider>
</Theme>
);
}

View File

@ -0,0 +1,13 @@
import { Container } from "@radix-ui/themes";
import React from "react";
export default function ArcticleViewer() {
return (
<>
<div className="ql-snow">
<Container className="mt-4 ql-editor">
</Container>
</div>
</>
);
}

View File

@ -0,0 +1,80 @@
import Quill, { Delta, } from "quill/core";
import ReactQuill from "react-quill";
import React, {
forwardRef,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import Sources from "quill";
type TEditor = {
readOnly?: boolean;
defaultValue?: string | Delta;
onChange: (d: string) => void; // TODO: make type
onSelectionChange?: any; // TODO same as before
};
const modules = {
toolbar: [
[{ header: [1, 2, 3, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
["link", "image"],
["clean"],
[{ align: [] }],
],
};
/**
* @param onChange - function that accepts Delta element
*/
const Editor = forwardRef((props: TEditor) => {
const editor = useRef(null);
const [quill, setQuill] = useState<Quill | null>(null);
const [value, setValue] = useState(new Delta())
useEffect(() => {
if (editor.current) {
//@ts-ignore
const temp = editor.current.getEditor() as Quill;
setQuill(temp);
}
return () => {
setQuill(null);
};
}, [editor.current]);
const changeHandler = (val: string, _changeDelta: Delta, _source: Sources, _editor: ReactQuill.UnprivilegedEditor) => {
console.log(val);
console.log(JSON.stringify(quill?.getContents().ops, null, 2))
let fullDelta = quill?.getContents()
props.onChange(val || "")
setValue(fullDelta || new Delta())
}
return (
<div className="text-editor">
<ReactQuill
value={value}
ref={editor}
modules={modules}
onChange={changeHandler}
theme="snow"
placeholder="Type your thoughts here..."
/>
</div>
);
});
export default Editor;

View File

@ -0,0 +1,67 @@
import { Button, Card, ChevronDownIcon, Text } from "@radix-ui/themes";
import * as NavigationMenu from "@radix-ui/react-navigation-menu";
import { useLocation, useNavigate } from "react-router-dom";
export default function NavBar() {
return (
<nav className="pt-2">
<NavigationMenu.Root
orientation="horizontal"
className="flex justify-center"
>
<NavigationMenu.List className="flex justify-center gap-2">
<NavItem text="Cringer" to="/" />
<NavItem text="C-Cringer" to="/c" />
<NavigationMenu.Item className="text-center">
<NavigationMenu.Trigger className="flex items-center">
<Button
asChild
className="w-fit pr-2 h-fit rounded-full m-0 p-0 pl-2 mt-2 mb-2 duration-[50ms]"
variant="ghost"
highContrast
>
<Text
size={"3"}
className="flex items-center gap-1"
>
Cringer 123 <ChevronDownIcon />
</Text>
</Button>
</NavigationMenu.Trigger>
<NavigationMenu.Content className="absolute data-[motion=from-start]:scale-150">
<Card>asd</Card>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>
</nav>
);
}
type TNavItem = {
text: string;
to: string;
};
function NavItem(props: TNavItem) {
const navigate = useNavigate();
const location = useLocation();
return (
<NavigationMenu.Item>
<NavigationMenu.Link>
<Button
className="w-fit h-fit rounded-full m-0 p-0 pr-2 pl-2 mt-2 mb-2 duration-[50ms]"
highContrast
variant={location.pathname === props.to ? "solid" : "ghost"}
onClick={() => navigate(props.to)}
>
<Text size={"3"}>{props.text}</Text>
</Button>
</NavigationMenu.Link>
</NavigationMenu.Item>
);
}

View File

@ -0,0 +1,23 @@
import React from 'react'
import { Outlet } from 'react-router-dom'
import NavBar from '../../Components/NavBar/NavBar'
import { axiosLocalhost } from '../../api/axios/axios'
export default function MainPage() {
return (
<>
<NavBar />
<Outlet />
<button
onClick={
async () => {
let d = await axiosLocalhost.get("getCookie")
console.log(d.data);
}
}>
qwpofjqwifhqwuif
</button>
</>
)
}

View File

@ -0,0 +1,14 @@
import {
QueryClient
} from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity
}
}
})
export default queryClient

View File

@ -0,0 +1,13 @@
import axios from "axios";
export const axiosLocalhost = axios.create(
{
baseURL: `http://localhost:9876/`,
withCredentials: true,
headers: {
}
}
)
axios.defaults.withCredentials = true;

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

17
enshi/src/index.css Normal file
View File

@ -0,0 +1,17 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.radix-themes {
--default-font-family:
--heading-font-family:
/* Your custom font for <Heading> components */
--code-font-family:
/* Your custom font for <Code> components */
--strong-font-family:
/* Your custom font for <Strong> components */
--em-font-family:
/* Your custom font for <Em> components */
--quote-font-family:
/* Your custom font for <Quote> components */;
}

5
enshi/src/locale/en.ts Normal file
View File

@ -0,0 +1,5 @@
const en = {
hello: "hello!"
}
export default en;

28
enshi/src/locale/i18n.ts Normal file
View File

@ -0,0 +1,28 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import ru from "./ru";
import en from "./en";
i18n
.use(initReactI18next)
.init({
fallbackLng: "ru",
lng: "ru",
debug: true,
resources: {
ru: {
translation: {...ru},
},
en: {
translation: {...en},
},
},
interpolation: {
escapeValue: false,
},
});
export default i18n;

5
enshi/src/locale/ru.ts Normal file
View File

@ -0,0 +1,5 @@
const ru = {
hello: "Привет!"
}
export default ru;

11
enshi/src/main.tsx Normal file
View File

@ -0,0 +1,11 @@
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import './locale/i18n.ts'
import i18n from './locale/i18n.ts'
i18n.changeLanguage(navigator.language)
createRoot(document.getElementById('root')!).render(
<App />
)

View File

@ -0,0 +1,27 @@
import { createRoutesFromElements, Route, useRouteError } from "react-router-dom"
import MainPage from "../Pages/MainPage/MainPage"
import {Text} from "@radix-ui/themes";
function ErrorBoundary() {
let error = useRouteError();
console.error(error);
return <div>Dang! This route does not exist... Yet ;)</div>;
}
export const routes = createRoutesFromElements(
<>
<Route
path="/"
errorElement={<ErrorBoundary />}
element={<MainPage />}
>
<Route index element={<Text>Cringer path</Text>} />
<Route
path="/a?/c"
element={<Text>Cringer path, but this a</Text>}
></Route>
</Route>
</>
)

1
enshi/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

21
enshi/tailwind.config.js Normal file
View File

@ -0,0 +1,21 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
animation: {
'appear': 'appear 0.25s'
},
keyframes: {
appear: {
'100%': {opacity: '1'}
}
}
},
},
plugins: [],
}

0
enshi/test Normal file
View File

24
enshi/tsconfig.app.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

7
enshi/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

22
enshi/tsconfig.node.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}

7
enshi/vite.config.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})

View File

@ -0,0 +1,42 @@
package rest_api_stuff
import (
"net/http"
"github.com/gin-gonic/gin"
)
func OkAnswer(c *gin.Context, message string) {
c.IndentedJSON(
http.StatusOK,
gin.H{"message": message},
)
}
func BadRequestAnswer(c *gin.Context, err error) {
c.IndentedJSON(
http.StatusBadRequest,
gin.H{"error": err.Error()},
)
}
func InternalErrorAnswer(c *gin.Context, err error) {
c.IndentedJSON(
http.StatusInternalServerError,
gin.H{"error": err.Error()},
)
}
func ConflictAnswer(c *gin.Context, err error) {
c.IndentedJSON(
http.StatusConflict,
gin.H{"error": err.Error()},
)
}
func UnauthorizedAnswer(c *gin.Context, err error) {
c.IndentedJSON(
http.StatusUnauthorized,
gin.H{"error": err.Error()},
)
}

View File

@ -0,0 +1,27 @@
package rest_api_stuff
import (
"github.com/gin-gonic/gin"
)
type CookieParams struct {
Name string
Value string
MaxAge int
Path string
Domain string
Secure bool
HttpOnly bool
}
func SetCookie(c *gin.Context, params *CookieParams) {
c.SetCookie(
params.Name,
params.Value,
params.MaxAge,
params.Path,
params.Domain,
params.Secure,
params.HttpOnly,
)
}

86
enshi_back/auth/jwt.go Normal file
View File

@ -0,0 +1,86 @@
package auth
import (
"fmt"
"strconv"
"time"
"github.com/golang-jwt/jwt/v5"
)
var (
// Secret key to sign JWT-tokens
SecretKey string
)
type UserInfoJWT struct {
Id int64
Username string
IsAdmin bool
}
// Generating new token with user info
//
// userInfo = { "id": int, "name": string }
func CreateToken(userInfo UserInfoJWT) (string, error) {
// Create new token
token := jwt.New(jwt.SigningMethodHS256)
// Get claims of this token (payload)
claims := token.Claims.(jwt.MapClaims)
// Add some info to claims
claims["username"] = userInfo.Username
claims["id"] = strconv.FormatInt(userInfo.Id, 10)
claims["isAdmin"] = userInfo.IsAdmin
claims["exp"] = time.Now().Add(time.Hour * 1).Unix()
// Get string token that will be passed to user
// We sign this token with SecretKey
tokenString, err := token.SignedString([]byte(SecretKey))
if err != nil {
return "", fmt.Errorf("error generate token: " + err.Error())
}
return tokenString, nil
}
// # Returns (claims, nil) if token is valid and all good
//
// # Returns (nil, error) if token is invalid for some reason
//
// Claims consists of name, id, exp(expiration time)
func ValidateToken(tokenSting string) (jwt.MapClaims, error) {
// Parsing string version of token to *jwt.Token
token, err := jwt.Parse(
// First arg -> string to parse
tokenSting,
// Second arg -> function that check hash method of token
// Return Secret key with what token string gonna be checked
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("wrong hash method: %v", token.Header["alg"])
}
return []byte(SecretKey), nil
})
if err != nil {
return nil, fmt.Errorf("error in token: %v", err.Error())
}
// Check token expiration time and if it is valid
// Get claims from parsed token
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
expirationTime := time.Unix(int64(claims["exp"].(float64)), 0)
if expirationTime.Before(time.Now()) {
return nil, fmt.Errorf("token has expired")
}
return claims, nil
} else {
return nil, fmt.Errorf("invalid token")
}
}

View File

@ -0,0 +1,123 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: blogs_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createBlogByUserId = `-- name: CreateBlogByUserId :one
INSERT INTO public.blogs
(blog_id, user_id, title, description, category_id, created_at)
VALUES($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
RETURNING blog_id, user_id, title, description, category_id, created_at
`
type CreateBlogByUserIdParams struct {
BlogID int64 `json:"blog_id"`
UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
}
func (q *Queries) CreateBlogByUserId(ctx context.Context, arg CreateBlogByUserIdParams) (Blog, error) {
row := q.db.QueryRow(ctx, createBlogByUserId,
arg.BlogID,
arg.UserID,
arg.Title,
arg.Description,
arg.CategoryID,
)
var i Blog
err := row.Scan(
&i.BlogID,
&i.UserID,
&i.Title,
&i.Description,
&i.CategoryID,
&i.CreatedAt,
)
return i, err
}
const deleteBlogByBlogId = `-- name: DeleteBlogByBlogId :exec
DELETE FROM public.blogs
WHERE blog_id=$1
`
func (q *Queries) DeleteBlogByBlogId(ctx context.Context, blogID int64) error {
_, err := q.db.Exec(ctx, deleteBlogByBlogId, blogID)
return err
}
const getBlogsByUserId = `-- name: GetBlogsByUserId :many
SELECT blog_id, user_id, title, description, category_id, created_at
FROM public.blogs
WHERE user_id = $1
`
func (q *Queries) GetBlogsByUserId(ctx context.Context, userID int64) ([]Blog, error) {
rows, err := q.db.Query(ctx, getBlogsByUserId, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Blog
for rows.Next() {
var i Blog
if err := rows.Scan(
&i.BlogID,
&i.UserID,
&i.Title,
&i.Description,
&i.CategoryID,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateBlogInfoByBlogId = `-- name: UpdateBlogInfoByBlogId :one
UPDATE public.blogs
SET title=$1, description=$2, category_id=$3
WHERE blog_id=$4
RETURNING blog_id, user_id, title, description, category_id, created_at
`
type UpdateBlogInfoByBlogIdParams struct {
Title pgtype.Text `json:"title"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
BlogID int64 `json:"blog_id"`
}
func (q *Queries) UpdateBlogInfoByBlogId(ctx context.Context, arg UpdateBlogInfoByBlogIdParams) (Blog, error) {
row := q.db.QueryRow(ctx, updateBlogInfoByBlogId,
arg.Title,
arg.Description,
arg.CategoryID,
arg.BlogID,
)
var i Blog
err := row.Scan(
&i.BlogID,
&i.UserID,
&i.Title,
&i.Description,
&i.CategoryID,
&i.CreatedAt,
)
return i, err
}

View File

@ -0,0 +1,103 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: bookmarks_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createBookmark = `-- name: CreateBookmark :one
INSERT INTO public.bookmarks
(user_id, post_id, bookmarked_at)
VALUES($1, $2, CURRENT_TIMESTAMP)
RETURNING user_id, post_id, bookmarked_at
`
type CreateBookmarkParams struct {
UserID int64 `json:"user_id"`
PostID int64 `json:"post_id"`
}
func (q *Queries) CreateBookmark(ctx context.Context, arg CreateBookmarkParams) (Bookmark, error) {
row := q.db.QueryRow(ctx, createBookmark, arg.UserID, arg.PostID)
var i Bookmark
err := row.Scan(&i.UserID, &i.PostID, &i.BookmarkedAt)
return i, err
}
const deleteBookmark = `-- name: DeleteBookmark :exec
DELETE FROM public.bookmarks
WHERE user_id=$1 AND post_id=$2
`
type DeleteBookmarkParams struct {
UserID int64 `json:"user_id"`
PostID int64 `json:"post_id"`
}
func (q *Queries) DeleteBookmark(ctx context.Context, arg DeleteBookmarkParams) error {
_, err := q.db.Exec(ctx, deleteBookmark, arg.UserID, arg.PostID)
return err
}
const getBookmarkTimestamp = `-- name: GetBookmarkTimestamp :one
SELECT bookmarked_at
FROM public.bookmarks bookmarks
where bookmarks.post_id = $1 and bookmarks.user_id = $2
`
type GetBookmarkTimestampParams struct {
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) GetBookmarkTimestamp(ctx context.Context, arg GetBookmarkTimestampParams) (pgtype.Timestamp, error) {
row := q.db.QueryRow(ctx, getBookmarkTimestamp, arg.PostID, arg.UserID)
var bookmarked_at pgtype.Timestamp
err := row.Scan(&bookmarked_at)
return bookmarked_at, err
}
const getBookmarksByUserId = `-- name: GetBookmarksByUserId :many
SELECT user_id, post_id, bookmarked_at
FROM public.bookmarks bookmarks
where bookmarks.user_id = $1
`
func (q *Queries) GetBookmarksByUserId(ctx context.Context, userID int64) ([]Bookmark, error) {
rows, err := q.db.Query(ctx, getBookmarksByUserId, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Bookmark
for rows.Next() {
var i Bookmark
if err := rows.Scan(&i.UserID, &i.PostID, &i.BookmarkedAt); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getCountOfBookmarksByPostId = `-- name: GetCountOfBookmarksByPostId :one
SELECT COUNT(*)
FROM public.bookmarks bookmarks
where bookmarks.post_id = $1
`
func (q *Queries) GetCountOfBookmarksByPostId(ctx context.Context, postID int64) (int64, error) {
row := q.db.QueryRow(ctx, getCountOfBookmarksByPostId, postID)
var count int64
err := row.Scan(&count)
return count, err
}

View File

@ -0,0 +1,74 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: categories_queries.sql
package db_repo
import (
"context"
)
const createCategory = `-- name: CreateCategory :one
INSERT INTO public.categories
(category_id, category_name)
VALUES($1, $2)
RETURNING category_id, category_name
`
type CreateCategoryParams struct {
CategoryID int32 `json:"category_id"`
CategoryName string `json:"category_name"`
}
func (q *Queries) CreateCategory(ctx context.Context, arg CreateCategoryParams) (Category, error) {
row := q.db.QueryRow(ctx, createCategory, arg.CategoryID, arg.CategoryName)
var i Category
err := row.Scan(&i.CategoryID, &i.CategoryName)
return i, err
}
const deleteCategoryById = `-- name: DeleteCategoryById :exec
DELETE FROM public.categories
WHERE category_id=$1
`
func (q *Queries) DeleteCategoryById(ctx context.Context, categoryID int32) error {
_, err := q.db.Exec(ctx, deleteCategoryById, categoryID)
return err
}
const getAllCategories = `-- name: GetAllCategories :many
SELECT category_id, category_name FROM public.categories
`
func (q *Queries) GetAllCategories(ctx context.Context) ([]Category, error) {
rows, err := q.db.Query(ctx, getAllCategories)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Category
for rows.Next() {
var i Category
if err := rows.Scan(&i.CategoryID, &i.CategoryName); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getCategoryByName = `-- name: GetCategoryByName :one
SELECT category_id, category_name FROM public.categories WHERE category_name = $1
`
func (q *Queries) GetCategoryByName(ctx context.Context, categoryName string) (Category, error) {
row := q.db.QueryRow(ctx, getCategoryByName, categoryName)
var i Category
err := row.Scan(&i.CategoryID, &i.CategoryName)
return i, err
}

View File

@ -0,0 +1,181 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: comments_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createComment = `-- name: CreateComment :one
INSERT INTO public."comments"
(comment_id, post_id, user_id, "content", created_at)
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP)
RETURNING comment_id, post_id, user_id, content, created_at
`
type CreateCommentParams struct {
CommentID int64 `json:"comment_id"`
PostID pgtype.Int8 `json:"post_id"`
UserID pgtype.Int8 `json:"user_id"`
Content pgtype.Text `json:"content"`
}
func (q *Queries) CreateComment(ctx context.Context, arg CreateCommentParams) (Comment, error) {
row := q.db.QueryRow(ctx, createComment,
arg.CommentID,
arg.PostID,
arg.UserID,
arg.Content,
)
var i Comment
err := row.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
)
return i, err
}
const deleteComment = `-- name: DeleteComment :exec
DELETE FROM public."comments"
WHERE comment_id = $1
`
func (q *Queries) DeleteComment(ctx context.Context, commentID int64) error {
_, err := q.db.Exec(ctx, deleteComment, commentID)
return err
}
const getCommentByUserId = `-- name: GetCommentByUserId :one
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".user_id = $1 and public."comments".post_id = $2
`
type GetCommentByUserIdParams struct {
UserID pgtype.Int8 `json:"user_id"`
PostID pgtype.Int8 `json:"post_id"`
}
func (q *Queries) GetCommentByUserId(ctx context.Context, arg GetCommentByUserIdParams) (Comment, error) {
row := q.db.QueryRow(ctx, getCommentByUserId, arg.UserID, arg.PostID)
var i Comment
err := row.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
)
return i, err
}
const getCommentsForPostAsc = `-- name: GetCommentsForPostAsc :many
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".post_id = $1
order by created_at ASC
LIMIT 10 offset ($2 * 10)
`
type GetCommentsForPostAscParams struct {
PostID pgtype.Int8 `json:"post_id"`
Column2 interface{} `json:"column_2"`
}
func (q *Queries) GetCommentsForPostAsc(ctx context.Context, arg GetCommentsForPostAscParams) ([]Comment, error) {
rows, err := q.db.Query(ctx, getCommentsForPostAsc, arg.PostID, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Comment
for rows.Next() {
var i Comment
if err := rows.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getCommentsForPostDesc = `-- name: GetCommentsForPostDesc :many
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".post_id = $1
order by created_at DESC
LIMIT 10 offset ($2 * 10)
`
type GetCommentsForPostDescParams struct {
PostID pgtype.Int8 `json:"post_id"`
Column2 interface{} `json:"column_2"`
}
func (q *Queries) GetCommentsForPostDesc(ctx context.Context, arg GetCommentsForPostDescParams) ([]Comment, error) {
rows, err := q.db.Query(ctx, getCommentsForPostDesc, arg.PostID, arg.Column2)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Comment
for rows.Next() {
var i Comment
if err := rows.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateCommentByCommentId = `-- name: UpdateCommentByCommentId :one
UPDATE public."comments"
SET "content"=$2
WHERE comment_id=$1
RETURNING comment_id, post_id, user_id, content, created_at
`
type UpdateCommentByCommentIdParams struct {
CommentID int64 `json:"comment_id"`
Content pgtype.Text `json:"content"`
}
func (q *Queries) UpdateCommentByCommentId(ctx context.Context, arg UpdateCommentByCommentIdParams) (Comment, error) {
row := q.db.QueryRow(ctx, updateCommentByCommentId, arg.CommentID, arg.Content)
var i Comment
err := row.Scan(
&i.CommentID,
&i.PostID,
&i.UserID,
&i.Content,
&i.CreatedAt,
)
return i, err
}

View File

@ -0,0 +1,32 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package db_repo
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
return &Queries{
db: tx,
}
}

View File

@ -0,0 +1,44 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: favorites_queries.sql
package db_repo
import (
"context"
)
const createFavorite = `-- name: CreateFavorite :one
INSERT INTO public.favorites
(user_id, blog_id, favorited_at)
VALUES($1, $2, CURRENT_TIMESTAMP)
RETURNING user_id, blog_id, favorited_at
`
type CreateFavoriteParams struct {
UserID int64 `json:"user_id"`
BlogID int64 `json:"blog_id"`
}
func (q *Queries) CreateFavorite(ctx context.Context, arg CreateFavoriteParams) (Favorite, error) {
row := q.db.QueryRow(ctx, createFavorite, arg.UserID, arg.BlogID)
var i Favorite
err := row.Scan(&i.UserID, &i.BlogID, &i.FavoritedAt)
return i, err
}
const deleteFavorite = `-- name: DeleteFavorite :exec
DELETE FROM public.favorites
WHERE user_id=$1 AND blog_id=$2
`
type DeleteFavoriteParams struct {
UserID int64 `json:"user_id"`
BlogID int64 `json:"blog_id"`
}
func (q *Queries) DeleteFavorite(ctx context.Context, arg DeleteFavoriteParams) error {
_, err := q.db.Exec(ctx, deleteFavorite, arg.UserID, arg.BlogID)
return err
}

View File

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: likes_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createLike = `-- name: CreateLike :one
INSERT INTO public.likes
(like_id, user_id, comment_id, created_at)
VALUES($1, $2, $3, CURRENT_TIMESTAMP)
RETURNING like_id, user_id, comment_id, created_at
`
type CreateLikeParams struct {
LikeID int64 `json:"like_id"`
UserID pgtype.Int8 `json:"user_id"`
CommentID pgtype.Int8 `json:"comment_id"`
}
func (q *Queries) CreateLike(ctx context.Context, arg CreateLikeParams) (Like, error) {
row := q.db.QueryRow(ctx, createLike, arg.LikeID, arg.UserID, arg.CommentID)
var i Like
err := row.Scan(
&i.LikeID,
&i.UserID,
&i.CommentID,
&i.CreatedAt,
)
return i, err
}
const deleteLikeByUserCommentId = `-- name: DeleteLikeByUserCommentId :exec
DELETE FROM public.likes
WHERE user_id = $1 and comment_id = $2
`
type DeleteLikeByUserCommentIdParams struct {
UserID pgtype.Int8 `json:"user_id"`
CommentID pgtype.Int8 `json:"comment_id"`
}
func (q *Queries) DeleteLikeByUserCommentId(ctx context.Context, arg DeleteLikeByUserCommentIdParams) error {
_, err := q.db.Exec(ctx, deleteLikeByUserCommentId, arg.UserID, arg.CommentID)
return err
}
const getLikesForComment = `-- name: GetLikesForComment :one
SELECT count(*)
FROM public.likes
WHERE comment_id = $1
`
func (q *Queries) GetLikesForComment(ctx context.Context, commentID pgtype.Int8) (int64, error) {
row := q.db.QueryRow(ctx, getLikesForComment, commentID)
var count int64
err := row.Scan(&count)
return count, err
}
const isUserLikedComment = `-- name: IsUserLikedComment :one
SELECT count(*)
FROM public.likes
WHERE user_id = $1 and comment_id = $2
`
type IsUserLikedCommentParams struct {
UserID pgtype.Int8 `json:"user_id"`
CommentID pgtype.Int8 `json:"comment_id"`
}
func (q *Queries) IsUserLikedComment(ctx context.Context, arg IsUserLikedCommentParams) (int64, error) {
row := q.db.QueryRow(ctx, isUserLikedComment, arg.UserID, arg.CommentID)
var count int64
err := row.Scan(&count)
return count, err
}

View File

@ -0,0 +1,92 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package db_repo
import (
"github.com/jackc/pgx/v5/pgtype"
)
type Blog struct {
BlogID int64 `json:"blog_id"`
UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Description pgtype.Text `json:"description"`
CategoryID pgtype.Int4 `json:"category_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}
type Bookmark struct {
UserID int64 `json:"user_id"`
PostID int64 `json:"post_id"`
BookmarkedAt pgtype.Timestamp `json:"bookmarked_at"`
}
type Category struct {
CategoryID int32 `json:"category_id"`
CategoryName string `json:"category_name"`
}
type Comment struct {
CommentID int64 `json:"comment_id"`
PostID pgtype.Int8 `json:"post_id"`
UserID pgtype.Int8 `json:"user_id"`
Content pgtype.Text `json:"content"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}
type Favorite struct {
UserID int64 `json:"user_id"`
BlogID int64 `json:"blog_id"`
FavoritedAt pgtype.Timestamp `json:"favorited_at"`
}
type Like struct {
LikeID int64 `json:"like_id"`
UserID pgtype.Int8 `json:"user_id"`
CommentID pgtype.Int8 `json:"comment_id"`
CreatedAt pgtype.Timestamp `json:"created_at"`
}
type Post struct {
PostID int64 `json:"post_id"`
BlogID pgtype.Int8 `json:"blog_id"`
UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
CreatedAt pgtype.Timestamp `json:"created_at"`
UpdatedAt pgtype.Timestamp `json:"updated_at"`
}
type PostTag struct {
PostID int64 `json:"post_id"`
TagID int32 `json:"tag_id"`
}
type PostVote struct {
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
Vote bool `json:"vote"`
}
type Profile struct {
UserID int64 `json:"user_id"`
Bio pgtype.Text `json:"bio"`
AvatarUrl pgtype.Text `json:"avatar_url"`
WebsiteUrl pgtype.Text `json:"website_url"`
}
type Tag struct {
TagID int32 `json:"tag_id"`
TagName string `json:"tag_name"`
}
type User struct {
UserID int64 `json:"user_id"`
Username string `json:"username" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
CreatedAt pgtype.Timestamp `json:"created_at"`
IsAdmin bool `json:"is_admin"`
}

View File

@ -0,0 +1,48 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: multi_queries.sql
package db_repo
import (
"context"
)
const getFavoriteBlogsByUserId = `-- name: GetFavoriteBlogsByUserId :many
SELECT blogs.blog_id, blogs.user_id, blogs.title, blogs.description, blogs.category_id, blogs.created_at
FROM favorites
JOIN blogs on blogs.blog_id = favorites.blog_id
WHERE favorites.user_id = $1
`
type GetFavoriteBlogsByUserIdRow struct {
Blog Blog `json:"blog"`
}
func (q *Queries) GetFavoriteBlogsByUserId(ctx context.Context, userID int64) ([]GetFavoriteBlogsByUserIdRow, error) {
rows, err := q.db.Query(ctx, getFavoriteBlogsByUserId, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetFavoriteBlogsByUserIdRow
for rows.Next() {
var i GetFavoriteBlogsByUserIdRow
if err := rows.Scan(
&i.Blog.BlogID,
&i.Blog.UserID,
&i.Blog.Title,
&i.Blog.Description,
&i.Blog.CategoryID,
&i.Blog.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -0,0 +1,75 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: post_tags_queries.sql
package db_repo
import (
"context"
)
const createPostTagRelation = `-- name: CreatePostTagRelation :one
INSERT INTO public.post_tags
(post_id, tag_id)
VALUES($1, $2)
RETURNING post_id, tag_id
`
type CreatePostTagRelationParams struct {
PostID int64 `json:"post_id"`
TagID int32 `json:"tag_id"`
}
func (q *Queries) CreatePostTagRelation(ctx context.Context, arg CreatePostTagRelationParams) (PostTag, error) {
row := q.db.QueryRow(ctx, createPostTagRelation, arg.PostID, arg.TagID)
var i PostTag
err := row.Scan(&i.PostID, &i.TagID)
return i, err
}
const deletePostTagRelation = `-- name: DeletePostTagRelation :exec
DELETE FROM public.post_tags
WHERE post_id = $1 AND tag_id = $2
`
type DeletePostTagRelationParams struct {
PostID int64 `json:"post_id"`
TagID int32 `json:"tag_id"`
}
func (q *Queries) DeletePostTagRelation(ctx context.Context, arg DeletePostTagRelationParams) error {
_, err := q.db.Exec(ctx, deletePostTagRelation, arg.PostID, arg.TagID)
return err
}
const getAllTagsForPost = `-- name: GetAllTagsForPost :many
SELECT tags.tag_id, tags.tag_name
from public.tags tags
JOIN public.post_tags post_tags on post_tags.tag_id = tags.tag_id
JOIN public.posts posts on posts.post_id = post_tags.post_id
`
type GetAllTagsForPostRow struct {
Tag Tag `json:"tag"`
}
func (q *Queries) GetAllTagsForPost(ctx context.Context) ([]GetAllTagsForPostRow, error) {
rows, err := q.db.Query(ctx, getAllTagsForPost)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetAllTagsForPostRow
for rows.Next() {
var i GetAllTagsForPostRow
if err := rows.Scan(&i.Tag.TagID, &i.Tag.TagName); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@ -0,0 +1,83 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: post_votes_queries.sql
package db_repo
import (
"context"
)
const createPostVote = `-- name: CreatePostVote :one
INSERT INTO public.post_votes
(post_id, user_id, vote)
VALUES($1, $2, $3)
RETURNING post_id, user_id, vote
`
type CreatePostVoteParams struct {
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
Vote bool `json:"vote"`
}
func (q *Queries) CreatePostVote(ctx context.Context, arg CreatePostVoteParams) (PostVote, error) {
row := q.db.QueryRow(ctx, createPostVote, arg.PostID, arg.UserID, arg.Vote)
var i PostVote
err := row.Scan(&i.PostID, &i.UserID, &i.Vote)
return i, err
}
const deletePostVote = `-- name: DeletePostVote :exec
DELETE FROM public.post_votes
WHERE post_id=$1 AND user_id=$2
`
type DeletePostVoteParams struct {
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) DeletePostVote(ctx context.Context, arg DeletePostVoteParams) error {
_, err := q.db.Exec(ctx, deletePostVote, arg.PostID, arg.UserID)
return err
}
const getPostVote = `-- name: GetPostVote :one
SELECT vote
FROM public.post_votes p_v
WHERE p_v.user_id = $1 and p_v.post_id = $2
`
type GetPostVoteParams struct {
UserID int64 `json:"user_id"`
PostID int64 `json:"post_id"`
}
func (q *Queries) GetPostVote(ctx context.Context, arg GetPostVoteParams) (bool, error) {
row := q.db.QueryRow(ctx, getPostVote, arg.UserID, arg.PostID)
var vote bool
err := row.Scan(&vote)
return vote, err
}
const updateVote = `-- name: UpdateVote :one
UPDATE public.post_votes
SET vote=$1
WHERE post_id=$2 AND user_id=$3
RETURNING post_id, user_id, vote
`
type UpdateVoteParams struct {
Vote bool `json:"vote"`
PostID int64 `json:"post_id"`
UserID int64 `json:"user_id"`
}
func (q *Queries) UpdateVote(ctx context.Context, arg UpdateVoteParams) (PostVote, error) {
row := q.db.QueryRow(ctx, updateVote, arg.Vote, arg.PostID, arg.UserID)
var i PostVote
err := row.Scan(&i.PostID, &i.UserID, &i.Vote)
return i, err
}

View File

@ -0,0 +1,183 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: posts_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createPost = `-- name: CreatePost :one
INSERT INTO public.posts
(post_id, blog_id, user_id, title, "content", created_at, updated_at)
VALUES($1, $2, $3, $4, $5, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
`
type CreatePostParams struct {
PostID int64 `json:"post_id"`
BlogID pgtype.Int8 `json:"blog_id"`
UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
}
func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) (Post, error) {
row := q.db.QueryRow(ctx, createPost,
arg.PostID,
arg.BlogID,
arg.UserID,
arg.Title,
arg.Content,
)
var i Post
err := row.Scan(
&i.PostID,
&i.BlogID,
&i.UserID,
&i.Title,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deletePostByPostId = `-- name: DeletePostByPostId :exec
DELETE FROM public.posts
WHERE post_id=$1
`
func (q *Queries) DeletePostByPostId(ctx context.Context, postID int64) error {
_, err := q.db.Exec(ctx, deletePostByPostId, postID)
return err
}
const getPostsByBlogId = `-- name: GetPostsByBlogId :many
SELECT post_id, blog_id, user_id, title, content, created_at, updated_at
FROM public.posts posts
where posts.blog_id = $1
`
func (q *Queries) GetPostsByBlogId(ctx context.Context, blogID pgtype.Int8) ([]Post, error) {
rows, err := q.db.Query(ctx, getPostsByBlogId, blogID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Post
for rows.Next() {
var i Post
if err := rows.Scan(
&i.PostID,
&i.BlogID,
&i.UserID,
&i.Title,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getPostsByPostId = `-- name: GetPostsByPostId :one
SELECT post_id, blog_id, user_id, title, content, created_at, updated_at
FROM public.posts posts
where posts.post_id = $1
`
func (q *Queries) GetPostsByPostId(ctx context.Context, postID int64) (Post, error) {
row := q.db.QueryRow(ctx, getPostsByPostId, postID)
var i Post
err := row.Scan(
&i.PostID,
&i.BlogID,
&i.UserID,
&i.Title,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getPostsByUserId = `-- name: GetPostsByUserId :many
SELECT post_id, blog_id, user_id, title, content, created_at, updated_at
FROM public.posts posts
where posts.user_id = $1
`
func (q *Queries) GetPostsByUserId(ctx context.Context, userID int64) ([]Post, error) {
rows, err := q.db.Query(ctx, getPostsByUserId, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Post
for rows.Next() {
var i Post
if err := rows.Scan(
&i.PostID,
&i.BlogID,
&i.UserID,
&i.Title,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updatePostByPostId = `-- name: UpdatePostByPostId :one
UPDATE public.posts
SET blog_id=$1, user_id=$2, title=$3, "content"=$4, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $5
RETURNING post_id, blog_id, user_id, title, content, created_at, updated_at
`
type UpdatePostByPostIdParams struct {
BlogID pgtype.Int8 `json:"blog_id"`
UserID int64 `json:"user_id"`
Title pgtype.Text `json:"title"`
Content pgtype.Text `json:"content"`
PostID int64 `json:"post_id"`
}
func (q *Queries) UpdatePostByPostId(ctx context.Context, arg UpdatePostByPostIdParams) (Post, error) {
row := q.db.QueryRow(ctx, updatePostByPostId,
arg.BlogID,
arg.UserID,
arg.Title,
arg.Content,
arg.PostID,
)
var i Post
err := row.Scan(
&i.PostID,
&i.BlogID,
&i.UserID,
&i.Title,
&i.Content,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}

View File

@ -0,0 +1,107 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: profiles_queries.sql
package db_repo
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const clearProfileByUserId = `-- name: ClearProfileByUserId :one
UPDATE public.profiles
SET bio='', avatar_url='', website_url=''
WHERE user_id=$1
RETURNING user_id, bio, avatar_url, website_url
`
func (q *Queries) ClearProfileByUserId(ctx context.Context, userID int64) (Profile, error) {
row := q.db.QueryRow(ctx, clearProfileByUserId, userID)
var i Profile
err := row.Scan(
&i.UserID,
&i.Bio,
&i.AvatarUrl,
&i.WebsiteUrl,
)
return i, err
}
const createProfileForUser = `-- name: CreateProfileForUser :one
INSERT INTO public.profiles
(user_id, bio, avatar_url, website_url)
VALUES($1, '', '', '')
RETURNING user_id, bio, avatar_url, website_url
`
func (q *Queries) CreateProfileForUser(ctx context.Context, userID int64) (Profile, error) {
row := q.db.QueryRow(ctx, createProfileForUser, userID)
var i Profile
err := row.Scan(
&i.UserID,
&i.Bio,
&i.AvatarUrl,
&i.WebsiteUrl,
)
return i, err
}
const deleteProfileByUserId = `-- name: DeleteProfileByUserId :exec
DELETE FROM public.profiles
WHERE user_id=$1
`
func (q *Queries) DeleteProfileByUserId(ctx context.Context, userID int64) error {
_, err := q.db.Exec(ctx, deleteProfileByUserId, userID)
return err
}
const getProfileByUserId = `-- name: GetProfileByUserId :one
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) {
row := q.db.QueryRow(ctx, getProfileByUserId, userID)
var i Profile
err := row.Scan(
&i.UserID,
&i.Bio,
&i.AvatarUrl,
&i.WebsiteUrl,
)
return i, err
}
const updateProfileByUserId = `-- name: UpdateProfileByUserId :one
UPDATE public.profiles
SET bio=$2, avatar_url=$3, website_url=$4
WHERE user_id=$1
RETURNING user_id, bio, avatar_url, website_url
`
type UpdateProfileByUserIdParams struct {
UserID int64 `json:"user_id"`
Bio pgtype.Text `json:"bio"`
AvatarUrl pgtype.Text `json:"avatar_url"`
WebsiteUrl pgtype.Text `json:"website_url"`
}
func (q *Queries) UpdateProfileByUserId(ctx context.Context, arg UpdateProfileByUserIdParams) (Profile, error) {
row := q.db.QueryRow(ctx, updateProfileByUserId,
arg.UserID,
arg.Bio,
arg.AvatarUrl,
arg.WebsiteUrl,
)
var i Profile
err := row.Scan(
&i.UserID,
&i.Bio,
&i.AvatarUrl,
&i.WebsiteUrl,
)
return i, err
}

View File

@ -0,0 +1,78 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: tags_queries.sql
package db_repo
import (
"context"
)
const createTag = `-- name: CreateTag :one
INSERT INTO public.tags
(tag_id, tag_name)
VALUES($1, $2)
RETURNING tag_id, tag_name
`
type CreateTagParams struct {
TagID int32 `json:"tag_id"`
TagName string `json:"tag_name"`
}
func (q *Queries) CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error) {
row := q.db.QueryRow(ctx, createTag, arg.TagID, arg.TagName)
var i Tag
err := row.Scan(&i.TagID, &i.TagName)
return i, err
}
const deleteTag = `-- name: DeleteTag :exec
DELETE FROM public.tags
WHERE tag_id = $1
`
func (q *Queries) DeleteTag(ctx context.Context, tagID int32) error {
_, err := q.db.Exec(ctx, deleteTag, tagID)
return err
}
const getAllTags = `-- name: GetAllTags :many
SELECT tag_id, tag_name
FROM public.tags
ORDER BY tag_name ASC
`
func (q *Queries) GetAllTags(ctx context.Context) ([]Tag, error) {
rows, err := q.db.Query(ctx, getAllTags)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Tag
for rows.Next() {
var i Tag
if err := rows.Scan(&i.TagID, &i.TagName); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getTagByTagId = `-- name: GetTagByTagId :one
SELECT tag_id, tag_name
FROM public.tags tags
where tags.tag_id = $1
`
func (q *Queries) GetTagByTagId(ctx context.Context, tagID int32) (Tag, error) {
row := q.db.QueryRow(ctx, getTagByTagId, tagID)
var i Tag
err := row.Scan(&i.TagID, &i.TagName)
return i, err
}

View File

@ -0,0 +1,179 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: users_queries.sql
package db_repo
import (
"context"
)
const createUser = `-- name: CreateUser :one
INSERT INTO public.users
(user_id, username, email, "password", created_at, is_admin)
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP, false)
RETURNING user_id, username, email, password, created_at, is_admin
`
type CreateUserParams struct {
UserID int64 `json:"user_id"`
Username string `json:"username" validate:"required"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required"`
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRow(ctx, createUser,
arg.UserID,
arg.Username,
arg.Email,
arg.Password,
)
var i User
err := row.Scan(
&i.UserID,
&i.Username,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.IsAdmin,
)
return i, err
}
const deleteUserById = `-- name: DeleteUserById :exec
DELETE FROM public.users
WHERE user_id=$1
`
func (q *Queries) DeleteUserById(ctx context.Context, userID int64) error {
_, err := q.db.Exec(ctx, deleteUserById, userID)
return err
}
const deleteUserByUsername = `-- name: DeleteUserByUsername :exec
DELETE FROM public.users
WHERE username=$1
`
func (q *Queries) DeleteUserByUsername(ctx context.Context, username string) error {
_, err := q.db.Exec(ctx, deleteUserByUsername, username)
return err
}
const getAllUsers = `-- name: GetAllUsers :many
SELECT user_id, username, email, password, created_at, is_admin FROM users
`
func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
rows, err := q.db.Query(ctx, getAllUsers)
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,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getUserByEmailOrNickname = `-- name: GetUserByEmailOrNickname :one
SELECT user_id, username, email, password, created_at, is_admin FROM users WHERE username = $1 OR email = $2 LIMIT 1
`
type GetUserByEmailOrNicknameParams struct {
Username string `json:"username" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
func (q *Queries) GetUserByEmailOrNickname(ctx context.Context, arg GetUserByEmailOrNicknameParams) (User, error) {
row := q.db.QueryRow(ctx, getUserByEmailOrNickname, arg.Username, arg.Email)
var i User
err := row.Scan(
&i.UserID,
&i.Username,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.IsAdmin,
)
return i, err
}
const getUserById = `-- name: GetUserById :one
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) {
row := q.db.QueryRow(ctx, getUserById, userID)
var i User
err := row.Scan(
&i.UserID,
&i.Username,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.IsAdmin,
)
return i, err
}
const getUserByUsername = `-- name: GetUserByUsername :one
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) {
row := q.db.QueryRow(ctx, getUserByUsername, username)
var i User
err := row.Scan(
&i.UserID,
&i.Username,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.IsAdmin,
)
return i, err
}
const updateUserPasswordHash = `-- name: UpdateUserPasswordHash :one
UPDATE public.users
SET "password"=$1
WHERE user_id=$2
RETURNING user_id, username, email, password, created_at, is_admin
`
type UpdateUserPasswordHashParams struct {
Password string `json:"password" validate:"required"`
UserID int64 `json:"user_id"`
}
func (q *Queries) UpdateUserPasswordHash(ctx context.Context, arg UpdateUserPasswordHashParams) (User, error) {
row := q.db.QueryRow(ctx, updateUserPasswordHash, arg.Password, arg.UserID)
var i User
err := row.Scan(
&i.UserID,
&i.Username,
&i.Email,
&i.Password,
&i.CreatedAt,
&i.IsAdmin,
)
return i, err
}

View 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");

View File

@ -0,0 +1,20 @@
-- name: CreateBlogByUserId :one
INSERT INTO public.blogs
(blog_id, user_id, title, description, category_id, created_at)
VALUES($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
RETURNING *;
-- name: UpdateBlogInfoByBlogId :one
UPDATE public.blogs
SET title=$1, description=$2, category_id=$3
WHERE blog_id=$4
RETURNING *;
-- name: GetBlogsByUserId :many
SELECT *
FROM public.blogs
WHERE user_id = $1;
-- name: DeleteBlogByBlogId :exec
DELETE FROM public.blogs
WHERE blog_id=$1;

View File

@ -0,0 +1,24 @@
-- name: GetBookmarksByUserId :many
SELECT *
FROM public.bookmarks bookmarks
where bookmarks.user_id = $1;
-- name: GetCountOfBookmarksByPostId :one
SELECT COUNT(*)
FROM public.bookmarks bookmarks
where bookmarks.post_id = $1;
-- name: GetBookmarkTimestamp :one
SELECT bookmarked_at
FROM public.bookmarks bookmarks
where bookmarks.post_id = $1 and bookmarks.user_id = $2;
-- name: CreateBookmark :one
INSERT INTO public.bookmarks
(user_id, post_id, bookmarked_at)
VALUES($1, $2, CURRENT_TIMESTAMP)
RETURNING *;
-- name: DeleteBookmark :exec
DELETE FROM public.bookmarks
WHERE user_id=$1 AND post_id=$2;

View File

@ -0,0 +1,16 @@
-- name: CreateCategory :one
INSERT INTO public.categories
(category_id, category_name)
VALUES($1, $2)
RETURNING *;
-- name: GetAllCategories :many
SELECT * FROM public.categories;
-- name: GetCategoryByName :one
SELECT * FROM public.categories WHERE category_name = $1;
-- name: DeleteCategoryById :exec
DELETE FROM public.categories
WHERE category_id=$1;

View File

@ -0,0 +1,34 @@
-- name: CreateComment :one
INSERT INTO public."comments"
(comment_id, post_id, user_id, "content", created_at)
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP)
RETURNING *;
-- name: DeleteComment :exec
DELETE FROM public."comments"
WHERE comment_id = $1;
-- name: GetCommentsForPostDesc :many
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".post_id = $1
order by created_at DESC
LIMIT 10 offset ($2 * 10);
-- name: GetCommentsForPostAsc :many
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".post_id = $1
order by created_at ASC
LIMIT 10 offset ($2 * 10);
-- name: UpdateCommentByCommentId :one
UPDATE public."comments"
SET "content"=$2
WHERE comment_id=$1
RETURNING *;
-- name: GetCommentByUserId :one
SELECT comment_id, post_id, user_id, "content", created_at
FROM public."comments"
where public."comments".user_id = $1 and public."comments".post_id = $2;

View File

@ -0,0 +1,9 @@
-- name: CreateFavorite :one
INSERT INTO public.favorites
(user_id, blog_id, favorited_at)
VALUES($1, $2, CURRENT_TIMESTAMP)
RETURNING *;
-- name: DeleteFavorite :exec
DELETE FROM public.favorites
WHERE user_id=$1 AND blog_id=$2;

View File

@ -0,0 +1,20 @@
-- name: CreateLike :one
INSERT INTO public.likes
(like_id, user_id, comment_id, created_at)
VALUES($1, $2, $3, CURRENT_TIMESTAMP)
RETURNING *;
-- name: DeleteLikeByUserCommentId :exec
DELETE FROM public.likes
WHERE user_id = $1 and comment_id = $2;
-- name: GetLikesForComment :one
SELECT count(*)
FROM public.likes
WHERE comment_id = $1;
-- name: IsUserLikedComment :one
SELECT count(*)
FROM public.likes
WHERE user_id = $1 and comment_id = $2;

View File

@ -0,0 +1,6 @@
-- name: GetFavoriteBlogsByUserId :many
SELECT sqlc.embed(blogs)
FROM favorites
JOIN blogs on blogs.blog_id = favorites.blog_id
WHERE favorites.user_id = $1;

View File

@ -0,0 +1,15 @@
-- name: GetAllTagsForPost :many
SELECT sqlc.embed(tags)
from public.tags tags
JOIN public.post_tags post_tags on post_tags.tag_id = tags.tag_id
JOIN public.posts posts on posts.post_id = post_tags.post_id;
-- name: CreatePostTagRelation :one
INSERT INTO public.post_tags
(post_id, tag_id)
VALUES($1, $2)
RETURNING *;
-- name: DeletePostTagRelation :exec
DELETE FROM public.post_tags
WHERE post_id = $1 AND tag_id = $2;

View File

@ -0,0 +1,20 @@
-- name: CreatePostVote :one
INSERT INTO public.post_votes
(post_id, user_id, vote)
VALUES($1, $2, $3)
RETURNING *;
-- name: DeletePostVote :exec
DELETE FROM public.post_votes
WHERE post_id=$1 AND user_id=$2;
-- name: UpdateVote :one
UPDATE public.post_votes
SET vote=$1
WHERE post_id=$2 AND user_id=$3
RETURNING *;
-- name: GetPostVote :one
SELECT vote
FROM public.post_votes p_v
WHERE p_v.user_id = $1 and p_v.post_id = $2;

View File

@ -0,0 +1,30 @@
-- name: GetPostsByPostId :one
SELECT *
FROM public.posts posts
where posts.post_id = $1;
-- name: GetPostsByUserId :many
SELECT *
FROM public.posts posts
where posts.user_id = $1;
-- name: GetPostsByBlogId :many
SELECT *
FROM public.posts posts
where posts.blog_id = $1;
-- name: CreatePost :one
INSERT INTO public.posts
(post_id, blog_id, user_id, title, "content", created_at, updated_at)
VALUES($1, $2, $3, $4, $5, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING *;
-- name: UpdatePostByPostId :one
UPDATE public.posts
SET blog_id=$1, user_id=$2, title=$3, "content"=$4, updated_at=CURRENT_TIMESTAMP
WHERE post_id = $5
RETURNING *;
-- name: DeletePostByPostId :exec
DELETE FROM public.posts
WHERE post_id=$1;

View File

@ -0,0 +1,25 @@
-- name: CreateProfileForUser :one
INSERT INTO public.profiles
(user_id, bio, avatar_url, website_url)
VALUES($1, '', '', '')
RETURNING *;
-- name: ClearProfileByUserId :one
UPDATE public.profiles
SET bio='', avatar_url='', website_url=''
WHERE user_id=$1
RETURNING *;
-- name: DeleteProfileByUserId :exec
DELETE FROM public.profiles
WHERE user_id=$1;
-- name: GetProfileByUserId :one
SELECT * FROM public.profiles WHERE user_id = $1;
-- name: UpdateProfileByUserId :one
UPDATE public.profiles
SET bio=$2, avatar_url=$3, website_url=$4
WHERE user_id=$1
RETURNING *;

View File

@ -0,0 +1,19 @@
-- name: GetTagByTagId :one
SELECT tag_id, tag_name
FROM public.tags tags
where tags.tag_id = $1;
-- name: CreateTag :one
INSERT INTO public.tags
(tag_id, tag_name)
VALUES($1, $2)
RETURNING *;
-- name: DeleteTag :exec
DELETE FROM public.tags
WHERE tag_id = $1;
-- name: GetAllTags :many
SELECT tag_id, tag_name
FROM public.tags
ORDER BY tag_name ASC;

View File

@ -0,0 +1,31 @@
-- name: GetAllUsers :many
SELECT * FROM users;
-- name: GetUserById :one
SELECT * FROM users WHERE user_id = $1;
-- name: GetUserByUsername :one
SELECT * FROM users WHERE username = $1;
-- name: CreateUser :one
INSERT INTO public.users
(user_id, username, email, "password", created_at, is_admin)
VALUES($1, $2, $3, $4, CURRENT_TIMESTAMP, false)
RETURNING *;
-- name: UpdateUserPasswordHash :one
UPDATE public.users
SET "password"=$1
WHERE user_id=$2
RETURNING *;
-- name: DeleteUserById :exec
DELETE FROM public.users
WHERE user_id=$1;
-- name: DeleteUserByUsername :exec
DELETE FROM public.users
WHERE username=$1;
-- name: GetUserByEmailOrNickname :one
SELECT * FROM users WHERE username = $1 OR email = $2 LIMIT 1;

24
enshi_back/db/sqlc.yml Normal file
View File

@ -0,0 +1,24 @@
version: "2"
sql:
- engine: "postgresql"
schema: "./migrations"
queries: "./queries"
gen:
go:
emit_json_tags: true
package: "db_repo"
out: "./go_queries"
sql_package: "pgx/v5"
overrides:
- column: users.password
go_struct_tag: validate:"required"
- column: users.username
go_struct_tag: validate:"required"
- column: users.email
go_struct_tag: validate:"required,email"
- db_type: "uuid"
go_type:
import: 'github.com/google/uuid'
type: 'UUID'

View File

@ -0,0 +1,47 @@
package db_connection
import (
"context"
db_repo "enshi/db/go_queries"
"enshi/env"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
)
// Pgx connection to database
var Dbx *pgxpool.Pool
var Dbx_connection *pgx.Conn
var Sqlc_query = db_repo.New(Dbx)
func SetupDatabase() error {
var bd_pass, bd_user string
var err error
if err := env.LookupEnv(&bd_pass, "BD_PASSWORD"); err != nil {
fmt.Printf("%v", err)
return err
}
if err := env.LookupEnv(&bd_user, "BD_USER"); err != nil {
fmt.Printf("%v", err)
return err
}
// Url to connect
url := fmt.Sprintf("postgres://%v:%v@nekiiinkognito.ru:5432/postgres", bd_user, bd_pass)
// Connecting to database
Dbx, err = pgxpool.New(context.Background(), url)
Dbx_connection, err = pgx.Connect(context.Background(), url)
if err != nil {
fmt.Println("Unable to connect")
fmt.Println(err)
} else {
fmt.Println("Connected successfully!")
}
return err
}

34
enshi_back/env/env.go vendored Normal file
View File

@ -0,0 +1,34 @@
package env
import (
"enshi/auth"
"fmt"
"os"
"github.com/joho/godotenv"
)
func LookupEnv(dest *string, envVar string) error {
if v, exists := os.LookupEnv(envVar); !exists {
return fmt.Errorf("%v not found in local env", envVar)
} else {
*dest = v
return nil
}
}
func LoadEnv(path string) error {
if err := godotenv.Load(path); err != nil {
fmt.Printf("%v\n", err)
return err
}
if err := LookupEnv(&auth.SecretKey, "SECRET_KEY"); err != nil {
fmt.Printf("%v\n", err)
return err
}
fmt.Println("Env loaded")
return nil
}

View File

@ -0,0 +1,11 @@
package global
var ResetColor = "\033[0m"
var RedColor = "\033[31m"
var GreenColor = "\033[32m"
var YellowColor = "\033[33m"
var BlueColor = "\033[34m"
var MagentaColor = "\033[35m"
var CyanColor = "\033[36m"
var GrayColor = "\033[37m"
var WhiteColor = "\033[97m"

View File

@ -0,0 +1,6 @@
package global
var PathForCookies = "/"
var DomainForCookies = "localhost"
var SecureForCookies = false
var HttpOnlyForCookies = false

45
enshi_back/go.mod Normal file
View File

@ -0,0 +1,45 @@
module enshi
go 1.23
require (
github.com/jackc/pgx/v5 v5.7.1
github.com/joho/godotenv v1.5.1
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

100
enshi_back/go.sum Normal file
View File

@ -0,0 +1,100 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -0,0 +1,158 @@
package hasher
import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"github.com/joho/godotenv"
"golang.org/x/crypto/argon2"
)
type Argon2Hash struct {
time uint32
memory uint32
threads uint8
keyLen uint32
saltLen uint32
}
type HashSalt struct {
Hash []byte
salt []byte
StringToStore string
}
// Initializer for algorithm
func NewArgon2Hash(time, keyLen, saltLen, memory uint32, threads uint8) *Argon2Hash {
return &Argon2Hash{
time: time,
keyLen: keyLen,
saltLen: saltLen,
memory: memory,
threads: threads,
}
}
// Generating salt (byte slice with pseudorandom symbols)
func SaltGen(length uint32) ([]byte, error) {
salt := make([]byte, length)
// Read pseudorandom numbers and write them in salt
_, err := rand.Read(salt)
if err != nil {
return nil, err
}
return salt, nil
}
// Generating hash with given password and salt
func (a *Argon2Hash) HashGen(password, salt []byte) (*HashSalt, error) {
var err error
// If salt len is zero => we hashing new password => we need new salt ;)
if len(salt) == 0 {
salt, err = SaltGen(a.saltLen)
}
if err != nil {
return nil, err
}
// Generating password hash with given params
hash := argon2.IDKey(password, salt, a.time, a.memory, a.threads, a.keyLen)
// Salt and hash is byte slice and to properly read them (as string) we need to use this functions
saltDecoded := base64.RawStdEncoding.EncodeToString(salt)
hashDecoded := base64.RawStdEncoding.EncodeToString(hash)
// Creating string with salt and password hash
stringToStore := fmt.Sprintf("$m=%d,t=%d$%s$%s", a.memory, a.time, saltDecoded, hashDecoded)
// This is unnecessary structure i created following the guide 0_0
return &HashSalt{Hash: hash, salt: salt, StringToStore: stringToStore}, nil
}
// Returns error if password hashes are not equal
func (a *Argon2Hash) Compare(hash, salt, password []byte) error {
// We hash given password with salt we created for original one
// so we can check wether hashes are equal
hashSalt, err := a.HashGen(password, salt)
if err != nil {
return err
}
// Comparing hashes
if !bytes.Equal(hash, hashSalt.Hash) {
return fmt.Errorf("invalid password (hashes does not match)")
}
return nil
}
// Function to extract hash and salt from password string
// that is stored in db
//
// returns hash, salt, error
func DecodeArgon2String(passwordHash string) ([]byte, []byte, error) {
values := strings.Split(passwordHash, "$")
// Transform string hash in byte slice
salt, err := base64.RawStdEncoding.Strict().DecodeString(values[2])
if err != nil {
return nil, nil, err
}
// Transform string hash in byte slice
hash, err := base64.RawStdEncoding.Strict().DecodeString(values[3])
if err != nil {
return nil, nil, err
}
return hash, salt, nil
}
var Argon2Hasher = NewArgon2Hash(2, 100, 32, 64*1024, 1)
func Test() {
pas := []byte("qwerty1")
testDbString := "$m=65536,t=2$u0h2MoT48NXJFIRQXW9/i0tNDu427RJv3vIeZQIm8FU$QtUmjmPhsBgWGloNQVRoFkLkHnQwuCqRVfgKA0Sm2QNMPc86vSLxQ/c8JUXroO37qwXdfC9DNvTnm/OOi7GfTBGnJJotLBlonG/9epAMGY453s9UZVeghmvftCagXzPS9QB3cg"
var salt []byte
cringe, err := Argon2Hasher.HashGen(pas, salt)
if err != nil {
return
}
storedH, storedS, err := DecodeArgon2String(testDbString)
if err != nil {
fmt.Println("CRINGE", err)
return
}
err = Argon2Hasher.Compare(storedH, storedS, []byte("qwerty1"))
if err != nil {
fmt.Println("DONT MATCH")
} else {
fmt.Println("MATCH")
}
if err := godotenv.Load("secrets.env"); err != nil {
fmt.Println("Error load .env")
}
// fmt.Println(testDbString)
fmt.Printf("%s", cringe.StringToStore)
fmt.Print("\n\n\n\n")
}

53
enshi_back/main.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"context"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/env"
utils "enshi/utils"
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
if err := env.LoadEnv("utils/secret.env"); err != nil {
fmt.Println(err.Error())
return
}
if err := db_connection.SetupDatabase(); err != nil {
fmt.Println(err.Error())
return
}
defer db_connection.Dbx.Close()
defer db_connection.Dbx_connection.Close(context.Background())
router := gin.Default()
if err := utils.SetupRotes(router); err != nil {
fmt.Println(err.Error())
return
}
// Transaction
tx, _ := db_connection.Dbx.Begin(context.Background())
defer tx.Rollback(context.Background())
repo := db_repo.New(tx)
users, _ := repo.GetAllUsers(context.Background())
for _, user := range users {
fmt.Printf("%v\n", user.Username)
}
if err := tx.Commit(context.Background()); err != nil {
return
}
router.Run("localhost:9876")
fmt.Printf("Hey!, %v", "you")
}

View File

@ -0,0 +1,10 @@
package middleware
import "github.com/gin-gonic/gin"
func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
}
}

View File

@ -0,0 +1,36 @@
package middleware
import (
"enshi/auth"
"net/http"
"github.com/gin-gonic/gin"
)
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// token := c.GetHeader("Authorization")
tokenFromCookies := c.Request.CookiesNamed("auth_cookie")[0].Value
cookieClimes, err := auth.ValidateToken(tokenFromCookies)
if err != nil {
c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
c.Abort()
return
}
// claims, err := auth.ValidateToken(token)
// if err != nil {
// c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()})
// c.Abort()
// return
// }
// Claims -> data stored in token
c.Set(ContextUserId, cookieClimes["id"])
c.Set(ContextTokenData, cookieClimes)
c.Next()
}
}

View File

@ -0,0 +1,25 @@
package middleware
import "github.com/gin-gonic/gin"
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set(
"Access-Control-Allow-Headers",
"Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, "+
"authorization, Authorization, accept, origin, Cache-Control, "+
"X-Requested-With, Cookie",
)
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
c.Writer.Header().Set("Access-Control-Expose-Headers", "Access-Token, Uid, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}

View File

@ -0,0 +1,44 @@
package getters
import (
"enshi/auth"
"enshi/global"
"enshi/middleware"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
func GetClaimsFromContext(c *gin.Context) (auth.UserInfoJWT, error) {
var UserInfo auth.UserInfoJWT
claims, exists := c.Get(middleware.ContextTokenData)
if !exists {
return auth.UserInfoJWT{}, fmt.Errorf("error getting user id")
}
parsedUserId, err := strconv.ParseInt(
claims.(jwt.MapClaims)["id"].(string),
10,
64,
)
if err != nil {
return auth.UserInfoJWT{}, fmt.Errorf("error parsing user id")
}
UserInfo.Id = parsedUserId
UserInfo.Username = claims.(jwt.MapClaims)["username"].(string)
isAdmin, err := strconv.ParseBool(claims.(jwt.MapClaims)["isAdmin"].(string))
if err != nil {
UserInfo.IsAdmin = false
fmt.Println(global.RedColor + "isAdmin prop corrupted" + global.ResetColor)
} else {
UserInfo.IsAdmin = isAdmin
}
return UserInfo, nil
}

View File

@ -0,0 +1,23 @@
package getters
import (
"enshi/middleware"
"fmt"
"strconv"
"github.com/gin-gonic/gin"
)
func GetUserIdFromContext(c *gin.Context) (int64, error) {
userId, exists := c.Get(middleware.ContextUserId)
if !exists {
return -1, fmt.Errorf("error getting user id")
}
if parsedUserId, err := strconv.ParseInt(userId.(string), 10, 64); err != nil {
return -1, fmt.Errorf("error parsing user id")
} else {
return parsedUserId, nil
}
}

View File

@ -0,0 +1,5 @@
package middleware
var ContextUserId = "id"
var ContextIsAdmin = "isAdmin"
var ContextTokenData = "tokenData"

View File

@ -0,0 +1,17 @@
package routes
import (
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"github.com/gin-gonic/gin"
)
func ChangeUserProfile(c *gin.Context) {
var userProfileParams db_repo.UpdateProfileByUserIdParams
if err := c.BindJSON(&userProfileParams); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
}
}

View File

@ -0,0 +1,44 @@
package routes
import (
"context"
"encoding/binary"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
func CreatePost(c *gin.Context) {
var postParams db_repo.CreatePostParams
if err := c.BindJSON(&postParams); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
userId, err := getters.GetUserIdFromContext(c)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
postParams.UserID = userId
if uuidForPost, err := uuid.NewV7(); err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
} else {
postParams.PostID = -int64(binary.BigEndian.Uint64(uuidForPost[8:]))
}
query := db_repo.New(db_connection.Dbx)
if _, err := query.CreatePost(context.Background(), postParams); err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
rest_api_stuff.OkAnswer(c, "Post has been created!")
}

View File

@ -0,0 +1,51 @@
package routes
import (
"context"
rest_api_stuff "enshi/REST_API_stuff"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/middleware/getters"
"fmt"
"github.com/gin-gonic/gin"
)
func DeletePost(c *gin.Context) {
var deletePostId struct {
postId int64
}
if err := c.BindJSON(&deletePostId); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
userClaims, err := getters.GetClaimsFromContext(c)
if err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
query := db_repo.New(db_connection.Dbx)
post, err := query.GetPostsByPostId(context.Background(), deletePostId.postId)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
if post.UserID != userClaims.Id {
rest_api_stuff.UnauthorizedAnswer(c, fmt.Errorf("you are not the author"))
return
}
// TODO: Add block of code, so admin could delete anything
err = query.DeletePostByPostId(context.Background(), deletePostId.postId)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
rest_api_stuff.OkAnswer(c, "post has been deleted")
}

View File

@ -0,0 +1,73 @@
package routes
import (
"context"
"enshi/auth"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/global"
"enshi/hasher"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func Login(c *gin.Context) {
type content struct {
Username string
Password string
}
var body content
err := c.BindJSON(&body)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"error 1st": err.Error()})
return
}
repo := db_repo.New(db_connection.Dbx)
user, err := repo.GetUserByUsername(context.Background(), body.Username)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
password_hash, salt, err := hasher.DecodeArgon2String(user.Password)
if err != nil {
c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
err = hasher.Argon2Hasher.Compare(password_hash, salt, []byte(body.Password))
if err != nil {
c.IndentedJSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
userInfo := auth.UserInfoJWT{
Id: user.UserID,
Username: user.Username,
IsAdmin: user.IsAdmin,
}
token, err := auth.CreateToken(userInfo)
if err != nil {
c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
cookieName := "auth_cookie"
cookieValue := token
maxAge := int(2 * time.Hour.Seconds()) // Cookie expiry time in seconds (1 hour)
path := global.PathForCookies // Cookie path
domain := global.DomainForCookies // Set domain (localhost for testing)
secure := global.SecureForCookies // Secure cookie (set to true in production with HTTPS)
httpOnly := global.HttpOnlyForCookies // HTTP only, so it can't be accessed by JavaScript
c.Header("Authorization", token)
c.SetCookie(cookieName, cookieValue, maxAge, path, domain, secure, httpOnly)
c.IndentedJSON(http.StatusOK, gin.H{"token": token})
}

View File

@ -0,0 +1,122 @@
package routes
import (
"context"
"encoding/binary"
rest_api_stuff "enshi/REST_API_stuff"
"enshi/auth"
db_repo "enshi/db/go_queries"
"enshi/db_connection"
"enshi/global"
"enshi/hasher"
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
)
func RegisterUser(c *gin.Context) {
var userParams db_repo.CreateUserParams
if err := c.BindJSON(&userParams); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
validate := validator.New(validator.WithRequiredStructEnabled())
if err := validate.Struct(userParams); err != nil {
rest_api_stuff.BadRequestAnswer(c, err)
return
}
query := db_repo.New(db_connection.Dbx)
sameNicknameOrEmailUser, _ := query.GetUserByEmailOrNickname(
context.Background(),
db_repo.GetUserByEmailOrNicknameParams{
Username: userParams.Username,
Email: userParams.Email,
},
)
if sameNicknameOrEmailUser.Username == userParams.Username {
rest_api_stuff.ConflictAnswer(
c,
fmt.Errorf("username"),
)
return
} else if sameNicknameOrEmailUser.Email == userParams.Email {
rest_api_stuff.ConflictAnswer(
c,
fmt.Errorf("email"),
)
return
}
transaction, err := db_connection.Dbx.Begin(context.Background())
defer transaction.Rollback(context.Background())
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
query_transaction := query.WithTx(transaction)
passwordHashSalt, err := hasher.Argon2Hasher.HashGen([]byte(userParams.Password), []byte{})
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
userParams.Password = passwordHashSalt.StringToStore
uuid, err := uuid.NewV7()
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
userParams.UserID = -int64(
binary.BigEndian.Uint64(uuid[8:]),
)
if _, err := query_transaction.CreateUser(context.Background(), userParams); err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
if _, err := query_transaction.CreateProfileForUser(
context.Background(),
userParams.UserID,
); err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
tokenParams := auth.UserInfoJWT{
Id: userParams.UserID,
Username: userParams.Username,
IsAdmin: false,
}
token, err := auth.CreateToken(tokenParams)
if err != nil {
rest_api_stuff.InternalErrorAnswer(c, err)
return
}
cookieParams := &rest_api_stuff.CookieParams{
Name: "auth_cookie",
Value: token,
MaxAge: int(2 * time.Hour.Seconds()),
Path: global.PathForCookies,
Domain: global.DomainForCookies,
Secure: global.SecureForCookies,
HttpOnly: global.HttpOnlyForCookies,
}
transaction.Commit(context.Background())
rest_api_stuff.SetCookie(c, cookieParams)
rest_api_stuff.OkAnswer(c, "User has been created!")
}

View File

@ -0,0 +1,40 @@
package utils
import (
"enshi/middleware"
"enshi/routes"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func testCookie(c *gin.Context) {
cock, _ := c.Cookie("auth_cookie")
c.IndentedJSON(http.StatusOK, gin.H{"token": "SLESAR' U STASA " + strings.Split(cock, "_")[0]})
}
func SetupRotes(g *gin.Engine) error {
g.Use(middleware.CORSMiddleware())
// Free group routes
freeGroup := g.Group("/")
freeGroup.GET("getCookie", testCookie)
freeGroup.POST("login", routes.Login)
freeGroup.POST("registerUser", routes.RegisterUser)
// Auth group routes
authGroup := g.Group("/")
authGroup.Use(middleware.AuthMiddleware())
authGroup.POST("createPost", routes.CreatePost)
authGroup.POST("deletePost", routes.DeletePost)
adminGroup := authGroup.Group("/admin/")
adminGroup.Use(middleware.AdminMiddleware())
authGroup.POST("changeUserProfile", routes.ChangeUserProfile)
return nil
}

171
package-lock.json generated Normal file
View File

@ -0,0 +1,171 @@
{
"name": "Enshi",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@tanstack/react-query": "^5.59.0",
"axios": "^1.7.7"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.59.0",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.0.tgz",
"integrity": "sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.59.0",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.0.tgz",
"integrity": "sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.59.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT",
"peer": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
}
}
}

6
package.json Normal file
View File

@ -0,0 +1,6 @@
{
"dependencies": {
"@tanstack/react-query": "^5.59.0",
"axios": "^1.7.7"
}
}