From e04933b9c108cc37d50fc0b36f747b95e27d3e76 Mon Sep 17 00:00:00 2001 From: FDKost Date: Thu, 18 Dec 2025 14:14:04 +0300 Subject: [PATCH] =?UTF-8?q?Refactored:=20-=20=D0=BF=D0=BE=D1=84=D0=B8?= =?UTF-8?q?=D0=BA=D1=88=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B3=20=D1=81=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B5?= =?UTF-8?q?=D0=B9;=20-=20=D0=BF=D0=BE=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=20READ?= =?UTF-8?q?ME.md,=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=80=D0=BE=D0=B1=D0=BD=D0=BE=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0;=20-=20=D0=BF=D0=BE=D1=87=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=20.env=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0.=20Checked:=20-=20docker-compose?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82;=20-=20auth?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82;=20-=20?= =?UTF-8?q?=D1=87=D0=B0=D1=82=20=D1=81=20=D0=BD=D0=B5=D0=B9=D1=80=D0=BE?= =?UTF-8?q?=D1=81=D0=B5=D1=82=D1=8C=D1=8E=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=B5=D1=82,=20=D0=BD=D0=BE=20=D0=BA=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20400=20=D0=B8=D0=B7=20=D0=B7=D0=B0=20NEWPLANET-AI?= =?UTF-8?q?-AGENTS,=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=D0=BD=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B8=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 2 +- new-planet-backend/.env | 31 ++++++++++--- new-planet-backend/README.md | 13 +++++- new-planet-backend/alembic/env.py | 19 +++----- new-planet-backend/app/core/security.py | 44 ++++++++++++++++--- new-planet-backend/app/db/base.py | 5 +-- new-planet-backend/app/models/reward.py | 2 +- new-planet-backend/app/schemas/user.py | 2 +- .../app/services/gigachat_service.py | 17 ++++++- new-planet-backend/requirements.txt | 1 + 10 files changed, 103 insertions(+), 33 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 31e1ebc..3c99962 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/new-planet-backend/.env b/new-planet-backend/.env index 103e017..e817043 100644 --- a/new-planet-backend/.env +++ b/new-planet-backend/.env @@ -1,7 +1,28 @@ +# Database +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=newplanet +POSTGRES_HOST=localhost +POSTGRES_PORT=5432 DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/newplanet + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_DB=0 REDIS_URL=redis://localhost:6379 -SECRET_KEY=your-secret-key-here -GIGACHAT_API_KEY=your-gigachat-api-key -MINIO_ENDPOINT=localhost:9000 -MINIO_ACCESS_KEY=minioadmin -MINIO_SECRET_KEY=minioadmin + +# Security +SECRET_KEY=jwt-secret-key + +# Storage (MinIO/S3) +STORAGE_ENDPOINT=localhost:9000 +STORAGE_ACCESS_KEY=minioadmin +STORAGE_SECRET_KEY=minioadmin +STORAGE_BUCKET=new-planet-images +STORAGE_USE_SSL=false +STORAGE_REGION=us-east-1 + +# GigaChat API +GIGACHAT_CLIENT_ID=gigachat-client-id +GIGACHAT_CLIENT_SECRET="gigachat-token-here" diff --git a/new-planet-backend/README.md b/new-planet-backend/README.md index c912ba2..7313f07 100644 --- a/new-planet-backend/README.md +++ b/new-planet-backend/README.md @@ -31,12 +31,17 @@ pip install -r requirements.txt 3. Настройте `.env`: ```bash cp .env.example .env +#В целом вам нужно поменять GIGACHAT API секцию, JWT Secret key сгенерить, просто в поисковике генератор на 256 байт сделаете JWT +#Для гигачата логинетесь, дергаете от туда CLIENT_ID и SECRET KEY # Отредактируйте .env с вашими настройками ``` 4. Запустите инфраструктуру (Docker): ```bash docker-compose -f docker/docker-compose.yml up -d +# или используйте вариант ниже,но лучше вариант выше для избежания непредвиденного +#также напоминаю что вам необходим сам запущенный докер чтобы тестировать локально +docker-compose up ``` 5. Примените миграции: @@ -47,6 +52,7 @@ alembic upgrade head 6. Запустите сервер: ```bash uvicorn app.main:app --reload +# если не запустилось, проверяйте есть ли .venv(установлено ли окружение для питона), также попробуйте в венве uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` API доступен на `http://localhost:8000` @@ -60,6 +66,9 @@ Swagger UI: `http://localhost:8000/docs` - `POST /api/v1/auth/refresh` - Обновление токена - `GET /api/v1/auth/me` - Текущий пользователь +### Для написания всех запросов ниже: +Не забывайте авторизоваться в сваггере, сверху кнопка Authorize + ### Schedules - `GET /api/v1/schedules` - Список расписаний - `POST /api/v1/schedules` - Создать расписание @@ -101,6 +110,6 @@ Swagger UI: `http://localhost:8000/docs` ## 🔗 Связанные репозитории -- **Frontend (Android)** — [new-planet-android](https://github.com/your-org/new-planet-android) -- **AI Agents** — [new-planet-ai-agents](https://github.com/your-org/new-planet-ai-agents) +- **Frontend (Android)** — [new-planet-android](https://git.bro-js.ru/Glevel/New-planet-app.git) +- **AI Agents** — [new-planet-ai-agents](https://git.bro-js.ru/Glevel/New-planet-ai-agent.git) diff --git a/new-planet-backend/alembic/env.py b/new-planet-backend/alembic/env.py index 1c7702a..274b381 100644 --- a/new-planet-backend/alembic/env.py +++ b/new-planet-backend/alembic/env.py @@ -1,9 +1,8 @@ from logging.config import fileConfig -from sqlalchemy import engine_from_config from sqlalchemy import pool from alembic import context import asyncio -from sqlalchemy.ext.asyncio import AsyncEngine +from sqlalchemy.ext.asyncio import create_async_engine # this is the Alembic Config object config = context.config @@ -22,12 +21,12 @@ target_metadata = Base.metadata def get_url(): """Получить URL БД""" - return settings.database_url.replace("+asyncpg", "") + return settings.database_url def run_migrations_offline() -> None: """Run migrations in 'offline' mode.""" - url = get_url() + url = get_url().replace("+asyncpg", "") context.configure( url=url, target_metadata=target_metadata, @@ -48,15 +47,9 @@ def do_run_migrations(connection): async def run_migrations_online() -> None: """Run migrations in 'online' mode.""" - configuration = config.get_section(config.config_ini_section) - configuration["sqlalchemy.url"] = get_url() - connectable = AsyncEngine( - engine_from_config( - configuration, - prefix="sqlalchemy.", - poolclass=pool.NullPool, - future=True, - ) + connectable = create_async_engine( + get_url(), + poolclass=pool.NullPool, ) async with connectable.connect() as connection: diff --git a/new-planet-backend/app/core/security.py b/new-planet-backend/app/core/security.py index e8e25cf..a4eb2c3 100644 --- a/new-planet-backend/app/core/security.py +++ b/new-planet-backend/app/core/security.py @@ -1,20 +1,54 @@ from datetime import datetime, timedelta from typing import Optional, Dict, Any from jose import JWTError, jwt -from passlib.context import CryptContext +import bcrypt from app.core.config import settings -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") - def verify_password(plain_password: str, hashed_password: str) -> bool: """Проверка пароля""" - return pwd_context.verify(plain_password, hashed_password) + # Используем bcrypt напрямую для проверки + try: + return bcrypt.checkpw(plain_password.encode('utf-8'), hashed_password.encode('utf-8')) + except (ValueError, TypeError, AttributeError): + return False + + +def _truncate_password_to_72_bytes(password: str) -> str: + """Обрезает пароль до 72 байт, корректно обрабатывая UTF-8""" + password_bytes = password.encode('utf-8') + if len(password_bytes) <= 72: + return password + + # Обрезаем до 72 байт + password_bytes = password_bytes[:72] + + # Удаляем неполные UTF-8 последовательности в конце + # (байты, которые начинаются с 10xxxxxx, но не являются началом символа) + while password_bytes and (password_bytes[-1] & 0xC0) == 0x80: + password_bytes = password_bytes[:-1] + + return password_bytes.decode('utf-8', errors='replace') def get_password_hash(password: str) -> str: """Хеширование пароля""" - return pwd_context.hash(password) + # bcrypt имеет ограничение в 72 байта + # Обрезаем пароль до 72 байт перед хешированием + password_bytes = password.encode('utf-8') + if len(password_bytes) > 72: + # Обрезаем до 72 байт + password_bytes = password_bytes[:72] + # Удаляем неполные UTF-8 последовательности в конце + while password_bytes and (password_bytes[-1] & 0xC0) == 0x80: + password_bytes = password_bytes[:-1] + password = password_bytes.decode('utf-8', errors='replace') + password_bytes = password.encode('utf-8') + + # Используем bcrypt напрямую, чтобы избежать проблем с инициализацией passlib + salt = bcrypt.gensalt() + hashed = bcrypt.hashpw(password_bytes, salt) + return hashed.decode('utf-8') def create_access_token(data: Dict[str, Any], expires_delta: Optional[timedelta] = None) -> str: diff --git a/new-planet-backend/app/db/base.py b/new-planet-backend/app/db/base.py index c0d1286..0435fb7 100644 --- a/new-planet-backend/app/db/base.py +++ b/new-planet-backend/app/db/base.py @@ -1,6 +1,5 @@ from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy import Column, DateTime, func -from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy import Column, DateTime, func, String import uuid Base = declarative_base() @@ -11,7 +10,7 @@ class BaseModel(Base): __abstract__ = True id = Column( - UUID(as_uuid=False), + String, primary_key=True, default=lambda: str(uuid.uuid4()), nullable=False diff --git a/new-planet-backend/app/models/reward.py b/new-planet-backend/app/models/reward.py index 2bf24e3..1e4d3a5 100644 --- a/new-planet-backend/app/models/reward.py +++ b/new-planet-backend/app/models/reward.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, String, Integer, ForeignKey, Text +from sqlalchemy import Column, String, Integer, ForeignKey, Text, Boolean from sqlalchemy.orm import relationship from app.db.base import BaseModel diff --git a/new-planet-backend/app/schemas/user.py b/new-planet-backend/app/schemas/user.py index 990d566..63ee08c 100644 --- a/new-planet-backend/app/schemas/user.py +++ b/new-planet-backend/app/schemas/user.py @@ -11,7 +11,7 @@ class UserBase(BaseModel): class UserCreate(UserBase): - password: str = Field(..., min_length=8) + password: str = Field(..., min_length=8, max_length=72, description="Password must be between 8 and 72 characters") class UserUpdate(BaseModel): diff --git a/new-planet-backend/app/services/gigachat_service.py b/new-planet-backend/app/services/gigachat_service.py index 57ce000..cd3ed52 100644 --- a/new-planet-backend/app/services/gigachat_service.py +++ b/new-planet-backend/app/services/gigachat_service.py @@ -1,4 +1,5 @@ import aiohttp +import ssl import base64 import uuid import time @@ -30,7 +31,13 @@ class GigaChatService: data = {"scope": "GIGACHAT_API_PERS"} - async with aiohttp.ClientSession() as session: + # Создаем SSL контекст без проверки сертификата (только для разработки!) + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + connector = aiohttp.TCPConnector(ssl=ssl_context) + async with aiohttp.ClientSession(connector=connector) as session: async with session.post( settings.GIGACHAT_AUTH_URL, headers=headers, @@ -75,7 +82,13 @@ class GigaChatService: "max_tokens": 2000 } - async with aiohttp.ClientSession() as session: + # Создаем SSL контекст без проверки сертификата (только для разработки!) + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + connector = aiohttp.TCPConnector(ssl=ssl_context) + async with aiohttp.ClientSession(connector=connector) as session: async with session.post( f"{settings.GIGACHAT_BASE_URL}/chat/completions", headers=headers, diff --git a/new-planet-backend/requirements.txt b/new-planet-backend/requirements.txt index 6430800..36c8bc9 100644 --- a/new-planet-backend/requirements.txt +++ b/new-planet-backend/requirements.txt @@ -3,6 +3,7 @@ uvicorn[standard] sqlalchemy>=2.0 alembic asyncpg +psycopg2-binary redis pydantic pydantic-settings