Compare commits

..

2 Commits

Author SHA1 Message Date
4cb6043931 Merge pull request 'Refactored:' (#1) from feature/projectUpped into master
Reviewed-on: #1
2025-12-18 14:33:08 +03:00
e04933b9c1 Refactored:
- пофикшен баг с авторизацией;
- поменен README.md, более подробно описан запуск проекта;
- починен .env для проекта.
Checked:
- docker-compose работает;
- auth работает;
- чат с нейросетью работает, но кидает 400 из за NEWPLANET-AI-AGENTS,нужно настроить подключение.
2025-12-18 14:14:04 +03:00
10 changed files with 103 additions and 33 deletions

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_22" project-jdk-name="openjdk-23" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -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"

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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,

View File

@@ -3,6 +3,7 @@ uvicorn[standard]
sqlalchemy>=2.0
alembic
asyncpg
psycopg2-binary
redis
pydantic
pydantic-settings