Merge branch 'divite_services'

modified:   .env
	modified:   .gitignore
	modified:   Makefile
	modified:   README.md
	deleted:    api/Makefile
	deleted:    certbot/Dockerfile
	deleted:    certbot/scripts/init-certbot.sh
	modified:   docker-compose.yaml
	new file:   easySite/easy-site/README.md
	new file:   easySite/easy-site/build/asset-manifest.json
	new file:   easySite/easy-site/build/index.html
	new file:   easySite/easy-site/build/static/js/main.9a8f7abd.js
	new file:   easySite/easy-site/build/static/js/main.9a8f7abd.js.LICENSE.txt
	new file:   easySite/easy-site/build/static/js/main.9a8f7abd.js.map
	new file:   easySite/easy-site/package-lock.json
	new file:   easySite/easy-site/package.json
	new file:   easySite/easy-site/postcss.config.js
	new file:   easySite/easy-site/public/index.html
	new file:   easySite/easy-site/src/App.css
	new file:   easySite/easy-site/src/App.js
	new file:   easySite/easy-site/src/App.test.js
	new file:   easySite/easy-site/src/contexts/AuthContext.js
	new file:   easySite/easy-site/src/contexts/useAuth.js
	new file:   easySite/easy-site/src/index.css
	new file:   easySite/easy-site/src/index.js
	new file:   easySite/easy-site/src/logo.svg
	new file:   easySite/easy-site/src/pages/AddObject.js
	new file:   easySite/easy-site/src/pages/Dashboard.js
	new file:   easySite/easy-site/src/pages/EditObject.js
	new file:   easySite/easy-site/src/pages/Home.js
	new file:   easySite/easy-site/src/pages/Login.js
	new file:   easySite/easy-site/src/pages/Register.js
	new file:   easySite/easy-site/src/reportWebVitals.js
	new file:   easySite/easy-site/src/setupTests.js
	new file:   easySite/easy-site/tailwind.config.js
	deleted:    nginx/nginx-ssl.conf
	new file:   node_servers/authserver.js
	new file:   node_servers/feedbackserver.js
	new file:   node_servers/techsupportserver.js
	new file:   serv_certbot/certbot/Dockerfile
	renamed:    certbot/config/certbot.ini -> serv_certbot/certbot/config/certbot.ini
	renamed:    certbot/scripts/checkRenewCerts.sh -> serv_certbot/certbot/scripts/checkRenewCerts.sh
	new file:   serv_certbot/certbot/scripts/crontab.txt
	new file:   serv_certbot/certbot/scripts/init-certbot.sh
	new file:   serv_certbot/certbot/scripts/renewEasysite102.sh
	renamed:    certbot/scripts/renewValitovGazizCert.sh -> serv_certbot/certbot/scripts/renewValitovGazizCert.sh
	renamed:    certbot/scripts/renewYalarbaCert.sh -> serv_certbot/certbot/scripts/renewYalarbaCert.sh
	new file:   serv_certbot/docker-compose.yml
	new file:   serv_migration/docker-compose.yml
	renamed:    migrator/Dockerfile -> serv_migration/migrator/Dockerfile
	renamed:    migrator/go.mod -> serv_migration/migrator/go.mod
	renamed:    migrator/go.sum -> serv_migration/migrator/go.sum
	renamed:    migrator/migrations/20240819162009_create_users_table.sql -> serv_migration/migrator/migrations/20240819162009_create_users_table.sql
	renamed:    migrator/migrations/20240825004755_add_column_role_to_users.sql -> serv_migration/migrator/migrations/20240825004755_add_column_role_to_users.sql
	new file:   serv_nginx/docker-compose.yml
	renamed:    nginx/Dockerfile -> serv_nginx/nginx/Dockerfile
	renamed:    nginx/nginx-http.conf -> serv_nginx/nginx/nginx-http.conf
	new file:   serv_nginx/nginx/nginx-ssl.conf
	renamed:    nginx/switch-config.sh -> serv_nginx/nginx/switch-config.sh
	renamed:    api/Dockerfile -> serv_rest_api/api/Dockerfile
	renamed:    api/bin/api.exe -> serv_rest_api/api/bin/api.exe
	renamed:    api/cmd/main.go -> serv_rest_api/api/cmd/main.go
	renamed:    api/go.mod -> serv_rest_api/api/go.mod
	renamed:    api/go.sum -> serv_rest_api/api/go.sum
	renamed:    api/src/configs/APIserver.go -> serv_rest_api/api/src/configs/APIserver.go
	renamed:    api/src/configs/DBconfig.go -> serv_rest_api/api/src/configs/DBconfig.go
	renamed:    api/src/initializers/PGQL_DB.go -> serv_rest_api/api/src/initializers/PGQL_DB.go
	renamed:    api/src/initializers/Routing.go -> serv_rest_api/api/src/initializers/Routing.go
	renamed:    api/src/models/Contacts.go -> serv_rest_api/api/src/models/Contacts.go
	renamed:    api/src/models/Essence.go -> serv_rest_api/api/src/models/Essence.go
	renamed:    api/src/models/Point.go -> serv_rest_api/api/src/models/Point.go
	renamed:    api/src/models/authDataStructs.go -> serv_rest_api/api/src/models/authDataStructs.go
	renamed:    api/src/models/user.go -> serv_rest_api/api/src/models/user.go
	renamed:    api/src/rt/admin/users.go -> serv_rest_api/api/src/rt/admin/users.go
	renamed:    api/src/rt/auth/Login.go -> serv_rest_api/api/src/rt/auth/Login.go
	renamed:    api/src/rt/auth/Registr.go -> serv_rest_api/api/src/rt/auth/Registr.go
	renamed:    api/src/rt/auth/authAdminMiddlware.go -> serv_rest_api/api/src/rt/auth/authAdminMiddlware.go
	renamed:    api/src/rt/auth/authMiddleware.go -> serv_rest_api/api/src/rt/auth/authMiddleware.go
	renamed:    api/src/rt/auth/auth_test.go -> serv_rest_api/api/src/rt/auth/auth_test.go
	renamed:    api/src/rt/prf/AuthUserMiddleWare.go -> serv_rest_api/api/src/rt/prf/AuthUserMiddleWare.go
	renamed:    api/src/rt/prf/profile.go -> serv_rest_api/api/src/rt/prf/profile.go
	renamed:    api/src/rt/srch/search.go -> serv_rest_api/api/src/rt/srch/search.go
	renamed:    api/src/storages/psql/psql.go -> serv_rest_api/api/src/storages/psql/psql.go
	new file:   serv_rest_api/docker-compose.yml
	new file:   serv_spa/docker-compose.yml
	new file:   serv_spa/spa/Dockerfile
	new file:   serv_spa/spa/app/assets/bage_logo.png
	new file:   serv_spa/spa/app/index.html
	new file:   serv_spa/spa/app/styles/mainStyle.css
	new file:   serv_spa/spa/vue/README.md
	new file:   serv_spa/spa/vue/advices.txt
This commit is contained in:
2025-08-08 16:12:54 +05:00
330 changed files with 47133 additions and 716 deletions
+5 -16
View File
@@ -1,14 +1,5 @@
PGHOST=db
PGPORT=5432
PGUSER=postgres
PGPASSWORD=postgres
PGDATABASE=postgres
SSLmode=disable
PGURL='postgres://postgres:postgres@db:5432/postgres?sslmode=disable'
# SERVER
SERVER_PORT=8000
SECRET_KEY=my_very_secret_key
# MIGRATOR # MIGRATOR
MIGRATOR_PORT=3000 MIGRATOR_PORT=3000
@@ -16,13 +7,11 @@ GOOSE_DRIVER=postgres
GOOSE_DBSTRING='user=postgres dbname=postgres sslmode=disable' GOOSE_DBSTRING='user=postgres dbname=postgres sslmode=disable'
GOOSE_MIGRATION_DIR=migrations GOOSE_MIGRATION_DIR=migrations
# FRONTEND SPA
HTTP_INNER_PORT=80
HTTP_OUTER_PORT=80
HTTPS_INNER_PORT=443
HTTPS_OUTER_PORT=443
# NGINX # NGINX
EMAIL=valitovgaziz@yandex.ru EMAIL=valitovgaziz@yandex.ru
DOMAINS=yalarba.ru,www.yalarba.ru DOMAINS_yalarba=yalarba.ru,www.yalarba.ru
DOMAINS_valitovgaziz=valitovgaziz.ru,www.valitovgaziz.ru DOMAINS_valitovgaziz=valitovgaziz.ru,www.valitovgaziz.ru
DOMAINS_easysite102=easysite102.ru,www.easysite102.ru
ALL_DOMAINS=yalarba.ru,www.yalarba.ru,valitovgaziz.ru,www.valitovgaziz.ru,easysite102.ru,www.easysite102.ru
+28 -2
View File
@@ -12,8 +12,8 @@ lerna-debug.log*
node_modules node_modules
.DS_Store .DS_Store
dist # dist
dist-ssr # dist-ssr
coverage coverage
*.local *.local
@@ -31,3 +31,29 @@ coverage
*.sw? *.sw?
*.tsbuildinfo *.tsbuildinfo
*.node_modules
*Makefile
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+3 -12
View File
@@ -1,19 +1,10 @@
build: build:
@docker compose --profile dev build --no-cache @docker compose build --no-cache
run: stop run: stop
@docker compose --profile dev up -d --remove-orphans @docker compose up -d --remove-orphans
stop: stop:
@docker compose --profile dev down @docker compose down
clean:
@docker builder --profile dev prune
test:
@go test ./api/src/auth/... -v
tc:
@go test -cover
.DEFAULT_GOAL=run .DEFAULT_GOAL=run
+7 -1
View File
@@ -5,5 +5,11 @@
## build and start with command: make ## build and start with command: make
Разделить на отдельные сервисы.
1. DB, rest api *
2. Migrator *
3. spa *
4. Certbot *
5. nginx *
need to see conf for nginx and change link to keycloak
-2
View File
@@ -1,2 +0,0 @@
t:
@go test ./... -v
-12
View File
@@ -1,12 +0,0 @@
FROM certbot/certbot
# Создаем директории для конфигов
RUN mkdir -p /etc/letsencrypt/config
# Копируем конфигурационные файлы
COPY scripts/ /opt/
# Устанавливаем права
RUN chmod +x /opt/*
ENTRYPOINT ["/opt/init-certbot.sh"]
-18
View File
@@ -1,18 +0,0 @@
#!/bin/sh
# Проверяем наличие сертификатов
if [ ! -d "/etc/letsencrypt/live/${DOMAINS}" ]; then
echo "Получаем новые сертификаты yalarba ..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS}
fi
if [ ! -d "/etc/letsencrypt/live/${DOMAINS_valitovgaziz}" ]; then
echo "Получаем новые сертификаты valitovgaziz ..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS_valitovgaziz}
fi
+37 -154
View File
@@ -1,162 +1,45 @@
services: services:
db: # keycloak:
image: postgres:16 # build:
container_name: db # context: ./keycloak
profiles: # dockerfile: Dockerfile
- prod # container_name: keycloak
env_file: # ports:
- .env # - "8080:8080"
ports: # environment:
- "${PGPORT}:${PGPORT}" # KC_ADMIN: admin
volumes: # KC_ADMIN_PASSWORD: admin
- postgres-db:/var/lib/postgresql/data # PROXY_ADDRESS_FORWARDING: true
environment: # KС_HTTPS_PORT: 8443
- POSTGRES_USER=${PGUSER} # KС_HTTP_PORT: 8080
- POSTGRES_PASSWORD=${PGPASSWORD} # KС_LOGLEVEL: INFO
- POSTGRES_DB=${PGDATABASE} # KС_HTTP_PROXY: true
restart: unless-stopped # KС_HTTPS_REDIRECT: false
# KC_HOSTNAME_STRICT: false
api: # KC_PROXY: passthrough
container_name: api # KC_HTTP_ENABLED: true
build: # KC_HOSTNAME: yalarba.ru
context: ./api # KC_HOSTNAME_STRICT_BACKCHANNEL: false
dockerfile: Dockerfile # KC_FRONTEND_URL: https://yalarba.ru/auth
profiles: # command: start --optimized
- prod # networks:
env_file: # - internal
- .env
ports:
- "${SERVER_PORT}:${SERVER_PORT}"
volumes:
- api:/usr/src/app
depends_on:
- db
command: ./bin/api
restart: unless-stopped
migrator: # kk_db:
container_name: migrator # image: postgres:16
profiles: # container_name: kk_db
- prod # environment:
build: # POSTGRES_DB: keycloak
context: ./migrator # POSTGRES_USER: postgres
dockerfile: Dockerfile # POSTGRES_PASSWORD: postgres
env_file: # volumes:
- .env # - keycloak-postgres:/var/lib/postgresql/data
depends_on: # networks:
- api # - internal
- db
volumes:
- goose:/migrations
command: goose up
spa:
container_name: spa
profiles:
- prod
build:
context: ./spa
dockerfile: Dockerfile
env_file:
- .env
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
profiles:
- prod
- dev
env_file: .env
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
- ./spa/app:/usr/share/nginx/html
- ./valitovgaziz/html:/usr/share/nginx/valitovgaziz/html
networks:
- web-network
- internal
depends_on:
- certbot
certbot:
build:
context: ./certbot
dockerfile: Dockerfile
container_name: certbot
profiles:
- prod
- dev
volumes:
- ./certbot/config:/etc/letsencrypt/config
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
env_file:
- .env
environment:
- EMAIL=valitovgaziz@yandex.ru
- DOMAINS=yalarba.ru
- STAGING=0
restart: unless-stopped
keycloak:
build:
context: ./keycloak
dockerfile: Dockerfile
container_name: keycloak
ports:
- "8080:8080"
profiles:
- prod
- dev
- kk
environment:
KC_ADMIN: admin
KC_ADMIN_PASSWORD: admin
PROXY_ADDRESS_FORWARDING: true
KС_HTTPS_PORT: 8443
KС_HTTP_PORT: 8080
KС_LOGLEVEL: INFO
KС_HTTP_PROXY: true
KС_HTTPS_REDIRECT: false
KC_HOSTNAME_STRICT: false
KC_PROXY: passthrough
KC_HTTP_ENABLED: true
KC_HOSTNAME: yalarba.ru
KC_HOSTNAME_STRICT_BACKCHANNEL: false
KC_FRONTEND_URL: https://yalarba.ru/auth
command: start --optimized
networks:
- internal
kk_db:
image: postgres:16
container_name: kk_db
profiles:
- prod
- dev
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- keycloak-postgres:/var/lib/postgresql/data
networks:
- internal
volumes: volumes:
api: keycloak-postgres:
postgres-db:
goose:
certbot_data:
certbot_www:
keycloak-postgres:
networks: networks:
web-network: web-network:
+70
View File
@@ -0,0 +1,70 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `npm start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
The page will reload when you make changes.\
You may also see any lint errors in the console.
### `npm test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `npm run build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `npm run eject`
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
### Analyzing the Bundle Size
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
### Making a Progressive Web App
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
### Advanced Configuration
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
### Deployment
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
### `npm run build` fails to minify
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
@@ -0,0 +1,10 @@
{
"files": {
"main.js": "/static/js/main.9a8f7abd.js",
"index.html": "/index.html",
"main.9a8f7abd.js.map": "/static/js/main.9a8f7abd.js.map"
},
"entrypoints": [
"static/js/main.9a8f7abd.js"
]
}
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>React App</title>
<script defer="defer" src="/static/js/main.9a8f7abd.js"></script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,60 @@
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react-jsx-runtime.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* react-router v7.7.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/
File diff suppressed because one or more lines are too long
+17602
View File
File diff suppressed because it is too large Load Diff
+40
View File
@@ -0,0 +1,40 @@
{
"name": "easy-site",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^13.5.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.7.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {}, // подключаем TailwindCSS
autoprefixer: {} // подключаем автопрефиксер
}
}
+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
+24
View File
@@ -0,0 +1,24 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './pages/Home'; // главная страница
import Login from './pages/Login'; // вход
import Register from './pages/Register'; // регистрация
import Dashboard from './pages/Dashboard'; // личный кабинет
import AddObject from './pages/AddObject'; // добавить объект
import EditObject from './pages/EditObject'; // редактировать объект
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/add-object" element={<AddObject />} />
<Route path="/edit-object/:id" element={<EditObject />} />
</Routes>
</Router>
);
}
export default App;
+8
View File
@@ -0,0 +1,8 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
@@ -0,0 +1,53 @@
// src/contexts/AuthContext.js
import React, { useState, createContext, useEffect } from 'react';
import axios from 'axios';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const login = async (email, password) => {
try {
const response = await axios.post('/api/login', { email, password });
if (response.data.token) {
localStorage.setItem('token', response.data.token); // сохраняем токен в Local Storage
setCurrentUser(response.data.user);
}
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
const register = async (name, email, password) => {
try {
const response = await axios.post('/api/register', { name, email, password });
if (response.data.token) {
localStorage.setItem('token', response.data.token);
setCurrentUser(response.data.user);
}
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
const logout = () => {
localStorage.removeItem('token');
setCurrentUser(null);
};
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
}
}, []);
return (
<AuthContext.Provider value={{ currentUser, login, register, logout }}>
{children}
</AuthContext.Provider>
);
};
export default AuthContext;
@@ -0,0 +1,7 @@
// src/contexts/useAuth.js
import { useContext } from 'react';
import AuthContext from './AuthContext';
export default function useAuth() {
return useContext(AuthContext);
}
+17
View File
@@ -0,0 +1,17 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+14
View File
@@ -0,0 +1,14 @@
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { AuthProvider } from './contexts/AuthContext';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</React.StrictMode>
);
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+35
View File
@@ -0,0 +1,35 @@
// src/pages/AddObject.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import useAuth from '../contexts/useAuth';
import axios from 'axios';
const AddObject = () => {
const auth = useAuth();
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const handleSubmit = async e => {
e.preventDefault();
try {
await axios.post('/api/add-object', { title, description }); // сюда ваш endpoint
alert("Объект успешно добавлен!");
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
return (
<div className="container">
<h2>Добавить объект</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Название" required onChange={(e) => setTitle(e.target.value)} /><br/><br/>
<textarea rows="4" cols="50" placeholder="Описание" required onChange={(e) => setDescription(e.target.value)} /><br/><br/>
<button type="submit">Добавить</button><br/><br/>
<Link to="/dashboard">Вернуться в личный кабинет</Link>
</form>
</div>
);
};
export default AddObject;
+58
View File
@@ -0,0 +1,58 @@
// src/pages/Dashboard.js
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import useAuth from '../contexts/useAuth';
import axios from 'axios';
const Dashboard = () => {
const auth = useAuth();
const [objects, setObjects] = useState([]);
useEffect(() => {
const fetchObjects = async () => {
try {
const response = await axios.get('/api/user/objects'); // заменить на нужный endpoint
setObjects(response.data.objects);
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
fetchObjects();
}, []);
// Функция подтверждения удаления объекта
const confirmDelete = objId => {
if (window.confirm('Вы действительно хотите удалить этот объект?')) {
deleteObject(objId);
}
};
// Функция удаления объекта
const deleteObject = async objId => {
try {
await axios.delete(`/api/delete-object/${objId}`); // заменить на правильный endpoint
setObjects(prevObjects => prevObjects.filter(obj => obj.id !== objId));
alert('Объект успешно удалён!');
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
return (
<div className="container">
<h2>Ваш личный кабинет</h2>
<ul>
{objects.map(obj => (
<li key={obj.id}>
Название: {obj.title}, Описание: {obj.description}<br/>
<Link to={`/edit-object/${obj.id}`}>Редактировать</Link>  <a href="#" onClick={() => confirmDelete(obj.id)}>Удалить</a>
</li>
))}
</ul>
<Link to="/add-object">Добавить объект</Link>
</div>
);
};
export default Dashboard;
@@ -0,0 +1,48 @@
// src/pages/EditObject.js
import React, { useState, useEffect } from 'react';
import { useParams, Link } from 'react-router-dom';
import useAuth from '../contexts/useAuth';
import axios from 'axios';
const EditObject = () => {
const auth = useAuth();
const { id } = useParams(); // получаем ID объекта из маршрута
const [object, setObject] = useState({});
useEffect(() => {
const fetchObject = async () => {
try {
const response = await axios.get(`/api/get-object/${id}`);
setObject(response.data.object);
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
fetchObject();
}, [id]);
const handleSubmit = async e => {
e.preventDefault();
try {
await axios.put(`/api/edit-object/${id}`, object); // тут ваш endpoint
alert("Объект успешно обновлён!");
} catch (err) {
console.error(err.response ? err.response.data : err.message);
}
};
return (
<div className="container">
<h2>Редактировать объект {id}</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Название" value={object.title || ""} onChange={(e) => setObject({...object, title: e.target.value})} /><br/><br/>
<textarea rows="4" cols="50" placeholder="Описание" value={object.description || ""} onChange={(e) => setObject({...object, description: e.target.value})} /><br/><br/>
<button type="submit">Обновить</button><br/><br/>
<Link to="/dashboard">Вернуться в личный кабинет</Link>
</form>
</div>
);
};
export default EditObject;
+13
View File
@@ -0,0 +1,13 @@
// src/pages/Home.js
import React from 'react';
const Home = () => {
return (
<div className="container">
<h1>Добро пожаловать!</h1>
<p>Это ваше стартовая страница.</p>
</div>
);
};
export default Home;
+28
View File
@@ -0,0 +1,28 @@
// src/pages/Login.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import useAuth from '../contexts/useAuth';
const Login = () => {
const auth = useAuth();
const [formData, setFormData] = useState({ email: '', password: '' });
const handleSubmit = async e => {
e.preventDefault();
await auth.login(formData.email, formData.password);
};
return (
<div className="container">
<h2>Вход</h2>
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Email" required onChange={(e) => setFormData({...formData, email: e.target.value})} />
<input type="password" placeholder="Пароль" required onChange={(e) => setFormData({...formData, password: e.target.value})} />
<button type="submit">Войти</button>
<p>Нет аккаунта? <Link to="/register">Зарегистрируйтесь</Link></p>
</form>
</div>
);
};
export default Login;
+29
View File
@@ -0,0 +1,29 @@
// src/pages/Register.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import useAuth from '../contexts/useAuth';
const Register = () => {
const auth = useAuth();
const [formData, setFormData] = useState({ name: '', email: '', password: '' });
const handleSubmit = async e => {
e.preventDefault();
await auth.register(formData.name, formData.email, formData.password);
};
return (
<div className="container">
<h2>Регистрация</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Имя" required onChange={(e) => setFormData({...formData, name: e.target.value})} />
<input type="email" placeholder="Email" required onChange={(e) => setFormData({...formData, email: e.target.value})} />
<input type="password" placeholder="Пароль" required onChange={(e) => setFormData({...formData, password: e.target.value})} />
<button type="submit">Зарегистрироваться</button>
<p>Уже зарегистрированы? <Link to="/login">Войти</Link></p>
</form>
</div>
);
};
export default Register;
+13
View File
@@ -0,0 +1,13 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
+5
View File
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
content: ['./src/**/*.{html,jsx,tsx}',],
theme: {
extend: {},
},
plugins: [],
}
-51
View File
@@ -1,51 +0,0 @@
server {
listen 80;
server_name yalarba.ru www.yalarba.ru auth.yalarba.ru;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name yalarba.ru;
ssl_certificate /etc/letsencrypt/live/yalarba.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yalarba.ru/privkey.pem;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /auth {
proxy_pass http://0.0.0.0:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
}
}
server {
listen 443 ssl;
server_name valitovgaziz.ru;
ssl_certificate /etc/letsencrypt/live/valitovgaziz.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/valitovgaziz.ru/privkey.pem;
location / {
root /usr/share/nginx/valitovgaziz/html;
index index.html;
}
}
+106
View File
@@ -0,0 +1,106 @@
const express = require('express');
const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const PORT = 6000;
const SECRET_KEY = '89044513447896254393432085332044367'; // Замените на свой секретный ключ
// Middleware
app.use(bodyParser.json());
app.use(cors());
// Подключение к PostgreSQL
const pool = new Pool({
user: 'postgres',
host: 'db',
database: 'postgres',
password: 'postgres',
port: 5432,
});
// Регистрация
app.post('/register', async (req, res) => {
try {
const { username, email, password } = req.body;
// Проверка, существует ли пользователь с таким email
const userExists = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
if (userExists.rows.length > 0) {
return res.status(400).json({ message: 'User with this email already exists' });
}
// Хеширование пароля
const hashedPassword = await bcrypt.hash(password, 10);
// Создание нового пользователя
const newUser = await pool.query(
'INSERT INTO users (username, email, password) VALUES ($1, $2, $3) RETURNING *',
[username, email, hashedPassword]
);
res.status(201).json({ message: 'User registered successfully', email: newUser.rows[0].email });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Something went wrong' });
}
});
// Авторизация
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// Поиск пользователя по email
const user = await pool.query('SELECT * FROM users WHERE email = $1', [email]);
if (user.rows.length === 0) {
return res.status(400).json({ message: 'User not found' });
}
// Проверка пароля
const isPasswordValid = await bcrypt.compare(password, user.rows[0].password);
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid credentials' });
}
// Создание JWT токена
const token = jwt.sign({ userId: user.rows[0].id }, SECRET_KEY, { expiresIn: '1h' });
res.status(200).json({ token });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Something went wrong' });
}
});
// Эндпоинт для проверки токенов
app.get('/check', async (req, res) => {
try {
// Извлекаем токен из заголовка Authorization
const authorizationHeader = req.headers.authorization;
if (!authorizationHeader) {
return res.status(401).send({ message: 'No token provided' });
}
// Разделяем строку на части: Bearer и сам токен
const token = authorizationHeader.split(' ')[1];
// Проверяем токен
const decodedToken = jwt.verify(token, SECRET_KEY);
// Если всё хорошо, отправляем положительный ответ
res.send({ message: 'Token is valid', userId: decodedToken.userId });
} catch (err) {
// Если произошла ошибка, возвращаем сообщение об ошибке
res.status(401).send({ message: err.message });
}
});
// Запуск сервера
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
})
+45
View File
@@ -0,0 +1,45 @@
const express = require('express');
const bodyParser = require('body-parser');
const { Pool } = require('pg');
const cors = require('cors');
const app = express();
const port = 3000;
app.use(cors());
app.use(bodyParser.json());
const pool = new Pool({
user: 'postgres',
host: 'db',
database: 'postgres',
password: 'postgres',
port: 5432,
});
app.get('/reviews', async(req, res) => {
try {
const result = await pool.query('SELECT * FROM reviews;');
res.json(result.rows);
} catch (err) {
console.error(err);
res.status(500).send("Server error");
}
});
app.post('/reviews', async(req, res) => {
const { text } = req.body;
try {
const result = await pool.query('INSERT INTO reviews (text) VALUES ($1) RETURNING *', [text]);
res.status(201).json(result.rows[0]);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
});
app.listen(port, '0.0.0.0', () => {
console.log("Server is running on port ${port}");
});
+44
View File
@@ -0,0 +1,44 @@
const express = require('express');
const bodyParser = require('body-parser');
const { Pool } = require('pg');
const cors = require('cors');
const app = express();
const port = 4000;
app.use(cors());
app.use(bodyParser.json());
const pool = new Pool({
user: 'postgres',
host: 'db',
database: 'postgres',
password: 'postgres',
port: 5432,
});
app.post('/support', async (req, res) => {
const { email, text } = req.body;
try {
const result = await pool.query('INSERT INTO posts (email, text) VALUES ($1, $2) RETURNING *', [email, text]);
res.status(201).json(result.rows[0]);
} catch (err) {
console.error(err);
res.status(500).send("Server error");
}
});
app.get('/support', async (req, res) => {
try {
const result = await pool.query('SELECT * FROM posts;');
res.json(result.rows);
} catch (err) {
console.error(err);
res.status(500).send("Server error");
}
});
app.listen(port, '0.0.0.0', () => {
console.log("Server is running on port ${port}");
});
+23
View File
@@ -0,0 +1,23 @@
FROM certbot/certbot
# устанавливать cron отдельно не нужно он уже есть в этом image certbot/certbot
# Создаем директории для конфигов
RUN mkdir -p /etc/letsencrypt/config
# Копируем конфигурационные файлы
COPY scripts/ /opt/
# Устанавливаем права
<<<<<<< HEAD:certbot/Dockerfile
RUN chmod +x /opt/*
=======
RUN chmod +x /opt/init-certbot.sh
RUN chmod +x /opt/checkRenewCerts.sh
RUN chmod +x /opt/renewEasysite102.sh
RUN chmod +x /opt/renewValitovGazizCert.sh
RUN chmod +x /opt/renewYalarbaCert.sh
>>>>>>> divite_services:serv_certbot/certbot/Dockerfile
ENTRYPOINT ["/opt/init-certbot.sh"]
@@ -11,7 +11,7 @@ agree-tos = True
non-interactive = True non-interactive = True
# Домены # Домены
domains = yalarba.ru,www.yalarba.ru,valitovaziz.ru,www.valitovgaziz.ru domains = yalarba.ru,www.yalarba.ru,valitovaziz.ru,www.valitovgaziz.ru,easysite102.ru,www.easysite102.ru
# Путь для webroot-аутентификации # Путь для webroot-аутентификации
webroot-path = /var/www/certbot webroot-path = /var/www/certbot
@@ -63,10 +63,23 @@ check_second_cert() {
fi fi
} }
# Проверка второго сертификата и запуск соответствующего сценария обновления
check_third_cert() {
local second_cert="/etc/letsencrypt/live/easysite102.ru/cert.pem"
local second_script="/opt/renewValitovGazizCert.sh"
local warning_days=2
if check_local_cert "$second_cert" "$warning_days"; then
echo "Запускаю обновление тетьего сертификата..."
"$third_script"
fi
}
# Основная функция для последовательной проверки обоих сертификатов # Основная функция для последовательной проверки обоих сертификатов
main() { main() {
check_first_cert check_first_cert
check_second_cert check_second_cert
check_third_cert
} }
# Запуск основной функции # Запуск основной функции
+1
View File
@@ -0,0 +1 @@
0 0 * * * root /opt/checkRenewCerts.sh > /proc/1/fd/1 2>&1
@@ -0,0 +1,40 @@
#!/bin/sh
# Проверяем наличие сертификатов для yalarba.ru
if [ ! -d "/etc/letsencrypt/live/${DOMAINS_yalarba}" ]; then
echo "Получаем новые сертификаты yalarba ..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS_yalarba}
fi
# Проверяем наличие сертификатов для valitovgaziz.ru
if [ ! -d "/etc/letsencrypt/live/${DOMAINS_valitovgaziz}" ]; then
echo "Получаем новые сертификаты valitovgaziz ..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS_valitovgaziz}
fi
# Проверяем наличие сертификатов для easysite102.ru
if [ ! -d "/etc/letsencrypt/live/${DOMAINS_easysite102}" ]; then
echo "Получаем новые сертификаты easysite102 ..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS_easysite102}
fi
set -e # Завершаем работу, если любая команда вернёт ошибку
# Активируем сервис cron
service cron start
# Копируем нашу собственную crontab таблицу
cp /opt/crontab.txt /etc/crontabs/root
# Оставляем контейнер открытым
tail -f /dev/null
@@ -0,0 +1,7 @@
#!/bin/sh
echo "Получаем новые сертификаты..."
certbot certonly --webroot \
--config /etc/letsencrypt/config/certbot.ini \
-w /var/www/certbot \
-d ${DOMAINS_easysite102}
+21
View File
@@ -0,0 +1,21 @@
services:
certbot:
build:
context: ./certbot
dockerfile: Dockerfile
container_name: certbot
volumes:
- ./certbot/config:/etc/letsencrypt/config
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
env_file:
- .env
environment:
- EMAIL=${EMAIL}
- DOMAINS=${ALL_DOMAINS}
- STAGING=0
restart: unless-stopped
volumes:
certbot_data:
certbot_www:
+17
View File
@@ -0,0 +1,17 @@
service:
migrator:
container_name: migrator
build:
context: ./migrator
dockerfile: Dockerfile
env_file:
- .env
depends_on:
- api
- db
volumes:
- goose:/migrations
command: goose up
volumes:
goose:
+31
View File
@@ -0,0 +1,31 @@
services:
nginx:
build:
context: ./nginx
dockerfile: Dockerfile
env_file: .env
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- certbot_data:/etc/letsencrypt
- certbot_www:/var/www/certbot
- ./spa/vue/dist:/usr/share/nginx/html
- ./valitovgaziz/html:/usr/share/nginx/valitovgaziz/html
- ./easysite/easysite/build:/usr/share/nginx/easysite/html
networks:
- web-network
- internal
depends_on:
- certbot
volumes:
certbot_data:
certbot_www:
networks:
web-network:
driver: bridge
internal:
+82
View File
@@ -0,0 +1,82 @@
server {
listen 80;
server_name yalarba.ru www.yalarba.ru valitovgaziz.ru www.valitovgaziz.ru easysite102.ru www.easysite102.ru;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name yalarba.ru www.yalarba.ru;
ssl_certificate /etc/letsencrypt/live/yalarba.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yalarba.ru/privkey.pem;
# Дополнительные SSL настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /auth {
proxy_pass http://0.0.0.0:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
}
}
server {
listen 443 ssl;
server_name valitovgaziz.ru www.valitovgaziz.ru;
ssl_certificate /etc/letsencrypt/live/valitovgaziz.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/valitovgaziz.ru/privkey.pem;
# Те же SSL настройки, что и выше
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
location / {
root /usr/share/nginx/valitovgaziz/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
server {
listen 443 ssl;
server_name easysite102.ru www.easysite102.ru;
ssl_certificate /etc/letsencrypt/live/easysite102.ru/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/easysite102.ru/privkey.pem;
# Те же SSL настройки
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
location / {
root /usr/share/nginx/easysite102/html;
index index.html;
try_files $uri $uri/ /index.html;
}
}
+40
View File
@@ -0,0 +1,40 @@
services:
db:
image: postgres:16
container_name: db
env_file:
- .env
ports:
- "${PGPORT}:${PGPORT}"
volumes:
- postgres-db:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${PGUSER}
- POSTGRES_PASSWORD=${PGPASSWORD}
- POSTGRES_DB=${PGDATABASE}
restart: unless-stopped
api:
container_name: api
build:
context: ./api
dockerfile: Dockerfile
env_file:
- .env
ports:
- "${SERVER_PORT}:${SERVER_PORT}"
volumes:
- api:/usr/src/app
depends_on:
- db
command: ./bin/api
restart: unless-stopped
volumes:
postgres-db:
api:
+8
View File
@@ -0,0 +1,8 @@
services:
spa:
container_name: spa
build:
context: ./spa
dockerfile: Dockerfile
env_file:
- .env
+11
View File
@@ -0,0 +1,11 @@
# Используем Node.js для сборки
FROM node:18-alpine as builde
WORKDIR /app
# Копируем package.json и package-lock.json для установки зависимостей
COPY vue/package*.json ./
RUN npm install
copy ./vue .
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+135
View File
@@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ЯлАрба - Туристический агрегатор</title>
<link rel="stylesheet" href="styles/mainStyle.css" />
<link rel="icon" href="assets/bage_logo.png" type="img/png" />
</head>
<body>
<!-- Yandex.Metrika counter -->
<script type="text/javascript">
(function (m, e, t, r, i, k, a) {
m[i] =
m[i] ||
function () {
(m[i].a = m[i].a || []).push(arguments);
};
m[i].l = 1 * new Date();
for (var j = 0; j < document.scripts.length; j++) {
if (document.scripts[j].src === r) {
return;
}
}
(k = e.createElement(t)),
(a = e.getElementsByTagName(t)[0]),
(k.async = 1),
(k.src = r),
a.parentNode.insertBefore(k, a);
})(
window,
document,
"script",
"https://mc.yandex.ru/metrika/tag.js",
"ym"
);
ym(103321366, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
});
</script>
<noscript
><div>
<img
src="https://mc.yandex.ru/watch/103321366"
style="position: absolute; left: -9999px"
alt="yandex metrika is disabled"
/></div
></noscript>
<!-- /Yandex.Metrika counter -->
<header>
<h1>ЯлАрба</h1>
<p>Инновационный агрегатор мест отдыха с ИИ-планировщиком маршрутов</p>
</header>
<section>
<h2>О проекте</h2>
<p>
ЯлАрба — это супер-приложение для путешественников, объединяющее в себе:
</p>
<ul>
<li>Агрегатор мест отдыха (отели, экскурсии, рестораны)</li>
<li>Маркетплейс туров и локальных сувениров</li>
<li>ИИ-планировщик маршрутов с учетом здоровья пользователя</li>
<li>Систему бронирования и покупки билетов</li>
</ul>
<p>
Наша миссия — сделать планирование путешествий простым,
персонализированным и доступным.
</p>
</section>
<div class="developer">
<h2>Разработчик</h2>
<p><strong>Валитов Газиз</strong></p>
<p>
Full-stack разработчик с опытом в travel-tech проектах. Специализация:
Python/Django, React, системы рекомендаций на основе ИИ.
</p>
</div>
<section class="roadmap">
<h2>План разработки</h2>
<div class="phase">
<h3>Этап 1: MVP (0-6 месяцев)</h3>
<ul>
<li>База мест отдыха (1000+ позиций)</li>
<li>Интеграция с API бронирования (Booking.com, Ostrovok)</li>
<li>Прототип ИИ-рекомендаций</li>
<li>Мобильная версия (PWA)</li>
</ul>
</div>
<div class="phase">
<h3>Этап 2: Монетизация (6-12 месяцев)</h3>
<ul>
<li>Система комиссий (10-15% с бронирований)</li>
<li>Платные подписки (PRO-аккаунт)</li>
<li>Партнерская программа для гидов</li>
</ul>
</div>
<div class="phase">
<h3>Этап 3: Масштабирование (12-24 месяца)</h3>
<ul>
<li>Выход на рынки СНГ (Казахстан, Узбекистан)</li>
<li>Полноценный ИИ-ассистент с рекомендациями по здоровью</li>
<li>Интеграция с сервисами аренды авто</li>
</ul>
</div>
</section>
<section>
<h2>Технологический стек</h2>
<ul>
<li><strong>Frontend:</strong> React.js, TypeScript, PWA</li>
<li><strong>Backend:</strong> Python/Django, PostgreSQL</li>
<li>
<strong>AI:</strong> NLP-модели для обработки запросов,
рекомендательные системы
</li>
<li><strong>DevOps:</strong> Docker, Kubernetes, Yandex Cloud</li>
</ul>
</section>
<footer>
<p>© 2024 ЯлАрба. Все права защищены.</p>
<p>Контакты: gaziz.valitov@yalarba.travel</p>
</footer>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f7fa;
}
header {
background-color: #1e88e5;
color: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 30px;
text-align: center;
}
h1 {
margin: 0;
font-size: 2.2em;
}
h2 {
color: #1e88e5;
border-bottom: 2px solid #1e88e5;
padding-bottom: 5px;
margin-top: 30px;
}
.developer {
background-color: #e3f2fd;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
}
.roadmap {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.phase {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px dashed #ccc;
}
.phase:last-child {
border-bottom: none;
}
footer {
text-align: center;
margin-top: 40px;
color: #666;
font-size: 0.9em;
}
+24
View File
@@ -0,0 +1,24 @@
### Есть задания для фрилансеров. Каждое задание 1000 рублей.
1. Задача настроить авторизацию через coocky no javaScript.
2. Хранение состояния через vuex.
3. Создать Makefile для работы со сборкой деплоем.
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```
+30
View File
@@ -0,0 +1,30 @@
Выбор шрифтов и размеров для мобильных устройств и десктопа зависит от множества факторов, таких как целевая аудитория, дизайн сайта/приложения и цели использования. Тем не менее, есть несколько общих рекомендаций, которые помогут обеспечить хорошую читаемость текста на разных устройствах.
### Шрифты
#### Для мобильных устройств:
- **Roboto**: Один из самых популярных шрифтов для Android. Он имеет четкий и чистый вид, хорошо подходит для чтения на небольших экранах.
- **San Francisco**: Стандартный шрифт для iOS. Хорошо сбалансирован и удобен для чтения.
- **Open Sans**: Универсальный шрифт, который отлично смотрится на любых устройствах благодаря своей нейтральной стилистике.
#### Для десктопных устройств:
- **Georgia**: Классический шрифт с засечками, который идеально подходит для длинных текстов. Обеспечивает комфортное чтение даже на больших экранах.
- **Lato**: Современный шрифт без засечек, легкий и элегантный. Подходит для заголовков и основного текста.
- **Merriweather**: Отличный выбор для длинных статей и блогов. Имеет хорошие пропорции и контрастность.
### Размеры шрифта
#### Мобильные устройства:
- **Основной текст**: 16px–18px. Это оптимальный размер для комфортного чтения на маленьких экранах.
- **Заголовки**: 20px24px для h2, 18px22px для h3, 16px20px для h4.
#### Десктопные устройства:
- **Основной текст**: 18px–20px. Такой размер обеспечивает удобство чтения на больших экранах.
- **Заголовки**: 28px32px для h2, 24px28px для h3, 20px24px для h4.
### Дополнительные советы:
- Используйте **относительные единицы измерения** (em, rem) вместо абсолютных (px), чтобы шрифт масштабировался вместе с изменением размера экрана.
- Следите за **межстрочным интервалом** (line-height). Оптимальное значение — около 1.5–1.6 для основного текста.
- Убедитесь, что у вас достаточно **контраста между текстом и фоном**. Черный текст на белом фоне — классический пример хорошего сочетания.
Эти рекомендации помогут создать удобный интерфейс как для мобильных пользователей, так и для тех, кто работает за компьютером.
+1
View File
@@ -0,0 +1 @@
import{_ as m,F as g,a as v}from"./footerB-DAi6c01a.js";import{u as p,o,c as n,b as c,t,r as b,a as l,d as k,F as u,e as f}from"./index-DccGIixi.js";const $={setup(){const{t:e}=p();return{t:e}}},x={class:"container"};function F(e,r,_,a,i,d){return o(),n("div",x,[c("h1",null,t(a.t("messages.tripFreedomAdventure.name")),1),c("p",null,t(a.t("messages.tripFreedomAdventure.text")),1)])}const w=m($,[["render",F],["__scopeId","data-v-aa005713"]]),y="/assets/photo_2025-01-25_05-57-24-BwgIchq6.jpg",A={setup(){const{t:e}=p();return{t:e}},methods:{donat(){window.open("https://www.tinkoff.ru/rm/r_fFXNVwPxmH.VMEQXkfWxO/Ho4MN83486","_blank")}},components:{FTA:w}},z={class:"about"};function C(e,r,_,a,i,d){const s=b("FTA");return o(),n("div",z,[l(s),c("p",null,t(a.t("messages.aboutL.donat-request")),1),c("button",{onClick:r[0]||(r[0]=(...h)=>d.donat&&d.donat(...h)),class:"donat-coffee"},t(a.t("messages.aboutL.donat-coffee")),1),r[1]||(r[1]=c("img",{src:y,alt:"Донат QRcod",class:"QRcod"},null,-1))])}const I=m(A,[["render",C],["__scopeId","data-v-6a219422"]]),B={setup(){const{t:e}=p();return{t:e}},data(){return{loading:!0,commits:[],error:null}},mounted(){this.fetchCommits()},methods:{async fetchCommits(){try{const e=await k.get("https://api.github.com/repos/valitovgaziz/spa_/commits",{headers:{Accept:"application/vnd.github.v3+json"}});this.commits=e.data}catch(e){this.error=e.message||"Произошла неизвестная ошибка"}finally{this.loading=!1}}}},L={key:0},V={key:1},N={key:2};function D(e,r,_,a,i,d){return i.loading?(o(),n("div",L,t(a.t("messages.commits.download")),1)):i.error?(o(),n("ul",V,t(a.t("messages.commits.errorOc"))+": "+t(i.error),1)):(o(),n("ul",N,[r[0]||(r[0]=c("h2",null,"Commits",-1)),(o(!0),n(u,null,f(i.commits,s=>(o(),n("li",{key:s.sha},[c("i",null,t(s.author.login)+" "+t(s.commit.author.date.slice(0,10))+" "+t(s.commit.message),1)]))),128))]))}const H=m(B,[["render",D],["__scopeId","data-v-ab2221db"]]),Q={setup(){const{t:e}=p();return{t:e}},name:"DevelopersList",data(){return{developers:[{nickname:"valitovgaziz",name:"Валитов Газиз",profileUrl:"https://github.com/valitovgaziz"}]}}},T={class:"developers"},j={class:"title"},q={class:"developer-list"},E=["href"];function M(e,r,_,a,i,d){return o(),n("div",T,[c("h1",j,t(a.t("messages.aboutL.developers")),1),c("ul",q,[(o(!0),n(u,null,f(i.developers,s=>(o(),n("li",{key:s.name,class:"developer-item"},[c("a",{href:s.profileUrl,target:"_blank",rel:"noopener noreferrer"},t(s.nickname)+" ("+t(s.name)+") ",9,E)]))),128))])])}const O=m(Q,[["render",M],["__scopeId","data-v-948e9ec5"]]),X={__name:"AboutView",setup(e){return(r,_)=>(o(),n(u,null,[l(g),l(I),l(O),l(H),l(v)],64))}};export{X as default};
+1
View File
@@ -0,0 +1 @@
.container[data-v-aa005713]{max-width:800px;margin:0 auto;padding:20px}h1[data-v-aa005713]{text-align:center;font-size:32px;margin-bottom:30px}p[data-v-aa005713]{line-height:1.6;font-size:18px}.additional-content[data-v-aa005713]{margin-top:40px}@media screen and (max-width: 600px){.container[data-v-aa005713]{padding:10px}h1[data-v-aa005713]{font-size:24px}p[data-v-aa005713]{font-size:16px}}.about[data-v-6a219422]{height:fit-content;display:flex;flex-direction:column;align-items:center;filter:none;padding:1rem;border-radius:1rem;margin:1rem}.donat-coffee[data-v-6a219422]{margin:1rem;background-color:#59db6d;box-shadow:1px 4px 8px #1b2d2533;border-radius:1rem}.donat-coffee[data-v-6a219422]:hover{cursor:pointer;background-color:#0a9632;color:#fff;box-shadow:1px 4px 8px #0a9632}.QRcod[data-v-6a219422]{height:200px;display:flexbox;border-radius:.5rem}ul[data-v-ab2221db]{max-width:1024px;list-style-type:none;padding:0}li[data-v-ab2221db]{margin:.5rem 0;white-space:pre-wrap;width:100%}.developers[data-v-948e9ec5]{max-width:600px;margin:0 auto;padding:20px}.title[data-v-948e9ec5]{text-align:center;font-size:24px;margin-bottom:20px}.developer-list[data-v-948e9ec5]{list-style-type:none;padding-left:0}.developer-item[data-v-948e9ec5]{margin-bottom:10px}.developer-item a[data-v-948e9ec5]{color:#007bff;text-decoration:none}.developer-item a[data-v-948e9ec5]:hover{text-decoration:underline}
+1
View File
@@ -0,0 +1 @@
import{_ as u,F as m,a as _}from"./footerB-DAi6c01a.js";import{u as p,d as v,o as n,c as w,b as r,t as l,w as g,v as b,F as h,e as R,a as c}from"./index-DccGIixi.js";const f={setup(){const{t:e}=p();return{t:e}},data(){return{reviews:[],newReview:"",showAll:!1}},computed:{displayedReviews(){return this.showAll?this.reviews:this.reviews.slice(0,5)}},mounted(){this.loadReviews()},methods:{async loadReviews(){try{const e=await v.get("https://yalarba.ru/api/reviews/reviews");this.reviews=e.data.sort((s,d)=>new Date(d.created_at)-new Date(s.created_at))}catch(e){console.error("Ошибка при загрузке отзывов:",e)}},async submitReview(){if(this.newReview.trim())try{const e=await v.post("https://yalarba.ru/api/reviews/reviews",{text:this.newReview});this.reviews.unshift(e.data),this.newReview=""}catch(e){console.error("Ошибка при отправке отзыва:",e)}},formatDate(e){return new Date(e).toLocaleString()},toggleShowAll(){this.showAll=!this.showAll}}},x={class:"reviews-container"},y={class:"add-review"},A=["placeholder"],k=["disabled"],D=["disabled"],F={class:"reviews-list"};function S(e,s,d,a,o,i){return n(),w("div",x,[r("h2",null,l(a.t("messages.reviews.h2review")),1),r("h4",null,l(a.t("messages.reviews.h4review")),1),r("div",y,[g(r("textarea",{"onUpdate:modelValue":s[0]||(s[0]=t=>o.newReview=t),placeholder:a.t("messages.reviews.textareaplaceholder"),rows:"4"},null,8,A),[[b,o.newReview]]),r("button",{onClick:s[1]||(s[1]=(...t)=>i.submitReview&&i.submitReview(...t)),disabled:!o.newReview.trim()},l(a.t("messages.reviews.button")),9,k),r("button",{onClick:s[2]||(s[2]=(...t)=>i.toggleShowAll&&i.toggleShowAll(...t)),disabled:o.reviews.length===0},l(o.showAll?a.t("messages.reviews.hide"):a.t("messages.reviews.viewAll")),9,D)]),r("div",F,[(n(!0),w(h,null,R(i.displayedReviews,t=>(n(),w("div",{key:t.id,class:"review-item"},[r("p",null,l(t.text),1),r("small",null,l(i.formatDate(t.created_at)),1)]))),128))])])}const B=u(f,[["render",S],["__scopeId","data-v-d1550644"]]),I={__name:"FeetbackView",setup(e){return(s,d)=>(n(),w(h,null,[c(m),c(B),c(_)],64))}};export{I as default};
@@ -0,0 +1 @@
.reviews-container[data-v-d1550644]{max-width:600px;margin:0 auto;padding:20px}.add-review textarea[data-v-d1550644]{width:90%;padding:10px;margin-bottom:10px;border-radius:5px;border:1px solid #ccc;background-color:var(--light-dark-background-color);box-shadow:1px 2px 3px #3d9a60}.add-review textarea[data-v-d1550644]::placeholder{color:var(--light-dark-text-color)}.add-review button[data-v-d1550644]{padding:10px 20px;background-color:var(--button-dark-background-color);color:var(--text-color);border:none;border-radius:.7rem;cursor:pointer;margin-right:10px;box-shadow:1px 2px 3px #3d9a60}.add-review button[data-v-d1550644]:disabled{background-color:var(--disabled-dark-background-color);cursor:not-allowed}.reviews-list[data-v-d1550644]{margin-top:2rem;width:90%}.review-item[data-v-d1550644]{background:var(--light-dark-background-color);padding:15px;border-radius:5px;margin-bottom:10px;box-shadow:1px 2px 3px #3d9a60}.review-item p[data-v-d1550644]{margin:0}.review-item small[data-v-d1550644]{color:var(--light-dark-text-color);font-size:.9em}
+1
View File
@@ -0,0 +1 @@
@font-face{font-family:OpenSansVariableFont;src:url(/assets/hinted-OpenSans-Regular-DZwYhniE.ttf) format("truetype-variations"),url(/assets/OpenSans-Regular-C74AleX8.woff) format("woff"),url(/assets/OpenSans-Regular-BT0WUJf-.woff2) format("woff2");font-weight:100 900;font-stretch:50% 200%}@font-face{font-family:OpenSansItalicVariableFont;src:url(/assets/hinted-OpenSans-Italic-BD2qe0ib.ttf) format("truetype-variations"),url(/assets/OpenSans-Italic-Cv5d4RQ-.woff) format("woff"),url(/assets/OpenSans-Italic-DNMplG0v.woff2) format("woff2");font-style:italic;font-weight:100 900;font-stretch:50% 200%}@font-face{font-family:RobotoVariableFont;src:url(/assets/hinted-Roboto-Regular-CFXqJ5DR.ttf) format("truetype-variations"),url(/assets/Roboto-Regular-n8vMMKqC.woff) format("woff"),url(/assets/Roboto-Regular-jtJqKL8A.woff2) format("woff2");font-weight:100 900;font-stretch:50% 200%}@font-face{font-family:RobotoItalicVarFont;src:url(/assets/hinted-Roboto-Italic-CDlkg4IH.ttf) format("truetype-variations"),url(/assets/Roboto-Italic-uZr_9iaA.woff) format("woff"),url(/assets/Roboto-Italic-DS1crIJz.woff2) format("woff2");font-style:italic;font-weight:100 900;font-stretch:50% 200%}:root{--font-primary: "OpenSansVariableFont", Arial, sans-serif;--font-secondary: "RobotoItalicVarFont", Arial, sans-serif;--weight-light: 300;--weight-normal: 400;--weight-bold: 700}body{font-family:var(--font-primary);font-weight:var(--weight-normal)}h1,h2,h3{font-family:var(--font-secondary);font-weight:var(--weight-light)}:root{--background-color: #ffffff;--text-color: #1c1d1d;--light-dark-background-color: #ffffff;--light-dark-text-color: #273f2c;--disabled-backgroud-color: rgba(23, 62, 31, .281);--button-text-color: #bac677;--disabled-dark-background-color: rgba(55, 64, 62, .2);--button-dark-background-color: #3cbf40;--light-dark-background-color: #8cd68f}[data-theme=dark]{--background-color: #282828;--text-color: #3cb756;--light-dark-background-color: #4e5e49;--light-dark-text-color: #59f37b;--disabled-dark-background-color: rgba(159, 193, 185, .2);--button-dark-background-color: #395e3a}a{color:inherit;text-decoration:none}a:visited{color:inherit}a:hover{text-decoration:none}a:active{color:inherit}html,body{background-color:var(--background-color);color:var(--text-color);margin:0;padding:0;height:fit-content;width:100%;font-family:Arial,sans-serif;transition:background-color .3s,color .3s;min-width:320px}body{display:flex;flex-direction:column;align-items:center;justify-content:center}.main{margin:2rem;min-height:90vh;min-width:320px;max-width:1024px;height:100%;width:100%;border-radius:1rem;display:flex;align-items:center;flex-direction:column;justify-content:space-between;padding:1rem 0 0}@media only screen and (max-width: 600px){.main{margin:.5rem;min-height:80vh;min-width:auto;max-width:90vw;width:90%;border-radius:.5rem}}
+1
View File
@@ -0,0 +1 @@
import{F as a,a as r}from"./footerB-DAi6c01a.js";import{S as o}from"./searchLine-3vDx-8Ye.js";import{c as t,a as e,F as s,o as c}from"./index-DccGIixi.js";const f={__name:"HomeView",setup(m){return(n,p)=>(c(),t(s,null,[e(a),e(o),e(r)],64))}};export{f as default};
+1
View File
@@ -0,0 +1 @@
import{_ as x,F as V,a as F}from"./footerB-DAi6c01a.js";import{i as d,u as S,j as B,k as I,l as L,m as s,o as c,c as m,t as n,b as e,w as h,v as g,f as A,a as p,F as M}from"./index-DccGIixi.js";const R={key:0},q={key:1,class:"logout"},C={key:2,class:"login-form"},D={class:"form-group"},E={for:"email"},N=["placeholder"],U={class:"form-group"},$={for:"password"},j=["placeholder"],G={type:"submit"},H={__name:"inout",setup(v){const l=d("");d(!0);const a=d("");d(!0);const{t:r}=S(),_=B(),i=I();let t=d(!1);L(async()=>{t=!0,i.checkAuth().catch(o=>console.error("Check auth failed",o)),t=!1});async function w(){if(t=!0,!b(l.value)||!y(a.value)){alert("Пожалуйста, заполните все поля корректно."),t=!1;return}try{await i.login({email:l.value,password:a.value}),t=!1,_.push("/")}catch(o){console.error(o),alert("Неверный email или пароль. Попробуйте снова.")}}function y(o){return!(a.length===0||a.length<6)}function b(o){return!(o.length===0||!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(o))}function k(){t=!0,i.logout(),t=!1,_.push("/")}return(o,u)=>s(t)?(c(),m("div",R,n(s(r)("messages.load"))+" . . .",1)):s(i).isAuthenticated?(c(),m("div",q,[e("div",null,[e("p",null,"Good by "+n(s(i).user.name)+"!",1),e("button",{onClick:k},"Logout")])])):(c(),m("div",C,[e("h1",null,n(s(r)("messages.inout.login")),1),e("form",{onSubmit:A(w,["prevent"])},[e("div",D,[e("label",E,n(s(r)("messages.inout.email"))+": ",1),h(e("input",{"onUpdate:modelValue":u[0]||(u[0]=f=>l.value=f),type:"email",id:"email",required:"",placeholder:s(r)("messages.inout.email")},null,8,N),[[g,l.value,void 0,{trim:!0}]])]),e("div",U,[e("label",$,n(s(r)("messages.inout.password"))+": ",1),h(e("input",{"onUpdate:modelValue":u[1]||(u[1]=f=>a.value=f),type:"password",id:"password",required:"",placeholder:s(r)("messages.inout.password")},null,8,j),[[g,a.value,void 0,{trim:!0}]])]),e("button",G,n(s(r)("messages.inout.login")),1)],32)]))}},P=x(H,[["__scopeId","data-v-1bd59691"]]),J={__name:"LogInView",setup(v){return(l,a)=>(c(),m(M,null,[p(V),p(P),p(F)],64))}};export{J as default};
+1
View File
@@ -0,0 +1 @@
.login-form[data-v-1bd59691]{padding:0 2rem 2rem;border:1px solid #ccc;border-radius:1rem;box-shadow:1px 2px 3px #609f7d}.form-group[data-v-1bd59691]{margin-bottom:1rem}.form-group label[data-v-1bd59691]{display:block}.form-group input[data-v-1bd59691]{padding:.7rem;border:none;border-radius:1rem;background-color:var(--light-dark-background-color);color:var(--text-color);box-shadow:1px 2px 3px #609f7d}button[data-v-1bd59691]{margin-top:.7rem;padding:.7rem 1.7rem;background-color:var(--button-dark-background-color);color:var(--text-color);border:none;border-radius:1rem;cursor:pointer;box-shadow:1px 2px 3px #609f7d}button[data-v-1bd59691]:hover{box-shadow:0 0 6px #609f7d}
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More