init
This commit is contained in:
1
new-planet-backend/app/api/__init__.py
Normal file
1
new-planet-backend/app/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
44
new-planet-backend/app/api/deps.py
Normal file
44
new-planet-backend/app/api/deps.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from typing import Optional
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.crud import user as crud_user
|
||||
from app.services.auth_service import auth_service
|
||||
from app.models.user import User
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth/login")
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(oauth2_scheme),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> User:
|
||||
"""Получить текущего пользователя из токена"""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
payload = auth_service.verify_token(token)
|
||||
if payload is None:
|
||||
raise credentials_exception
|
||||
|
||||
user_id: str = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise credentials_exception
|
||||
|
||||
user = await crud_user.get(db, user_id)
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> User:
|
||||
"""Получить активного пользователя"""
|
||||
return current_user
|
||||
|
||||
4
new-planet-backend/app/api/v1/__init__.py
Normal file
4
new-planet-backend/app/api/v1/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from app.api.v1.router import api_router
|
||||
|
||||
__all__ = ["api_router"]
|
||||
|
||||
60
new-planet-backend/app/api/v1/ai.py
Normal file
60
new-planet-backend/app/api/v1/ai.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.models.user import User
|
||||
from app.schemas.ai import ChatRequest, ChatResponse, ScheduleGenerateRequest, ScheduleGenerateResponse
|
||||
from app.services.chat_service import chat_service
|
||||
from app.services.schedule_generator import schedule_generator
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/chat", response_model=ChatResponse)
|
||||
async def chat_with_ai(
|
||||
request: ChatRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Чат с ИИ-агентом 'Планета Земля'"""
|
||||
try:
|
||||
response = await chat_service.chat(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
request=request
|
||||
)
|
||||
return response
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Chat error: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.post("/schedule/generate", response_model=ScheduleGenerateResponse)
|
||||
async def generate_schedule_ai(
|
||||
request: ScheduleGenerateRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Сгенерировать расписание через ИИ"""
|
||||
try:
|
||||
result = await schedule_generator.generate(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
child_age=request.child_age,
|
||||
preferences=request.preferences,
|
||||
schedule_date=request.date,
|
||||
description=request.description
|
||||
)
|
||||
return ScheduleGenerateResponse(
|
||||
schedule_id=result["schedule_id"],
|
||||
title=result["title"],
|
||||
tasks=result["tasks"]
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to generate schedule: {str(e)}"
|
||||
)
|
||||
|
||||
63
new-planet-backend/app/api/v1/auth.py
Normal file
63
new-planet-backend/app/api/v1/auth.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Body
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.schemas.user import UserCreate, User
|
||||
from app.schemas.token import Token
|
||||
from app.services.auth_service import auth_service
|
||||
from app.api.deps import get_current_active_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/register", response_model=User, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
user_in: UserCreate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Регистрация нового пользователя"""
|
||||
try:
|
||||
user = await auth_service.register(db, user_in)
|
||||
return user
|
||||
except ValueError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=str(e)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Аутентификация пользователя"""
|
||||
token = await auth_service.authenticate(db, form_data.username, form_data.password)
|
||||
if not token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect email or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
@router.post("/refresh", response_model=Token)
|
||||
async def refresh_token(
|
||||
refresh_token: str = Body(..., embed=True)
|
||||
):
|
||||
"""Обновление access token"""
|
||||
new_access_token = auth_service.refresh_access_token(refresh_token)
|
||||
if not new_access_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
return Token(access_token=new_access_token, token_type="bearer")
|
||||
|
||||
|
||||
@router.get("/me", response_model=User)
|
||||
async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
||||
"""Получить информацию о текущем пользователе"""
|
||||
return current_user
|
||||
|
||||
66
new-planet-backend/app/api/v1/images.py
Normal file
66
new-planet-backend/app/api/v1/images.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.models.user import User
|
||||
from app.services.storage_service import storage_service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
async def upload_image(
|
||||
file: UploadFile = File(...),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""Загрузить изображение"""
|
||||
# Проверка типа файла
|
||||
if not file.content_type or not file.content_type.startswith("image/"):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be an image"
|
||||
)
|
||||
|
||||
# Проверка размера (макс 10MB)
|
||||
file_content = await file.read()
|
||||
if len(file_content) > 10 * 1024 * 1024:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File size must be less than 10MB"
|
||||
)
|
||||
|
||||
try:
|
||||
# Загружаем файл
|
||||
from io import BytesIO
|
||||
file_obj = BytesIO(file_content)
|
||||
url = await storage_service.upload_file(
|
||||
file_obj=file_obj,
|
||||
filename=file.filename or "image.jpg",
|
||||
content_type=file.content_type
|
||||
)
|
||||
return {"url": url, "filename": file.filename}
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to upload image: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{file_key}")
|
||||
async def delete_image(
|
||||
file_key: str,
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""Удалить изображение"""
|
||||
try:
|
||||
success = await storage_service.delete_file(file_key)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="File not found"
|
||||
)
|
||||
return {"message": "File deleted successfully"}
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to delete image: {str(e)}"
|
||||
)
|
||||
|
||||
135
new-planet-backend/app/api/v1/rewards.py
Normal file
135
new-planet-backend/app/api/v1/rewards.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.models.user import User
|
||||
from app.schemas.reward import Reward, RewardCreate, RewardUpdate
|
||||
from app.crud.base import CRUDBase
|
||||
from app.models.reward import Reward as RewardModel
|
||||
|
||||
router = APIRouter()
|
||||
reward_crud = CRUDBase(RewardModel)
|
||||
|
||||
|
||||
@router.get("", response_model=List[Reward])
|
||||
async def get_rewards(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=100),
|
||||
is_claimed: bool = Query(None),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить список наград пользователя"""
|
||||
filters = {"user_id": current_user.id}
|
||||
if is_claimed is not None:
|
||||
filters["is_claimed"] = is_claimed
|
||||
|
||||
rewards = await reward_crud.get_multi(db, skip=skip, limit=limit, filters=filters)
|
||||
return rewards
|
||||
|
||||
|
||||
@router.get("/{reward_id}", response_model=Reward)
|
||||
async def get_reward(
|
||||
reward_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить награду по ID"""
|
||||
reward = await reward_crud.get(db, reward_id)
|
||||
if not reward:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Reward not found"
|
||||
)
|
||||
if reward.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
return reward
|
||||
|
||||
|
||||
@router.post("", response_model=Reward, status_code=status.HTTP_201_CREATED)
|
||||
async def create_reward(
|
||||
reward_in: RewardCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Создать новую награду"""
|
||||
reward_data = reward_in.model_dump()
|
||||
reward_data["user_id"] = current_user.id
|
||||
reward = await reward_crud.create(db, reward_data)
|
||||
return reward
|
||||
|
||||
|
||||
@router.put("/{reward_id}", response_model=Reward)
|
||||
async def update_reward(
|
||||
reward_id: str,
|
||||
reward_in: RewardUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Обновить награду"""
|
||||
reward = await reward_crud.get(db, reward_id)
|
||||
if not reward:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Reward not found"
|
||||
)
|
||||
if reward.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
update_data = reward_in.model_dump(exclude_unset=True)
|
||||
reward = await reward_crud.update(db, reward, update_data)
|
||||
return reward
|
||||
|
||||
|
||||
@router.delete("/{reward_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_reward(
|
||||
reward_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Удалить награду"""
|
||||
reward = await reward_crud.get(db, reward_id)
|
||||
if not reward:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Reward not found"
|
||||
)
|
||||
if reward.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
await reward_crud.delete(db, reward_id)
|
||||
return None
|
||||
|
||||
|
||||
@router.patch("/{reward_id}/claim", response_model=Reward)
|
||||
async def claim_reward(
|
||||
reward_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить награду (отметить как полученную)"""
|
||||
reward = await reward_crud.get(db, reward_id)
|
||||
if not reward:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Reward not found"
|
||||
)
|
||||
if reward.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
reward = await reward_crud.update(db, reward, {"is_claimed": True})
|
||||
return reward
|
||||
|
||||
13
new-planet-backend/app/api/v1/router.py
Normal file
13
new-planet-backend/app/api/v1/router.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from fastapi import APIRouter
|
||||
from app.api.v1 import auth, schedules, tasks, rewards, images, ai, websocket
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
|
||||
api_router.include_router(schedules.router, prefix="/schedules", tags=["schedules"])
|
||||
api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"])
|
||||
api_router.include_router(rewards.router, prefix="/rewards", tags=["rewards"])
|
||||
api_router.include_router(images.router, prefix="/images", tags=["images"])
|
||||
api_router.include_router(ai.router, prefix="/ai", tags=["ai"])
|
||||
api_router.include_router(websocket.router, prefix="/ws", tags=["websocket"])
|
||||
|
||||
141
new-planet-backend/app/api/v1/schedules.py
Normal file
141
new-planet-backend/app/api/v1/schedules.py
Normal file
@@ -0,0 +1,141 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from datetime import date
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.models.user import User
|
||||
from app.schemas.schedule import Schedule, ScheduleCreate, ScheduleUpdate
|
||||
from app.crud import schedule as crud_schedule
|
||||
from app.services.schedule_generator import schedule_generator
|
||||
from app.schemas.ai import ScheduleGenerateRequest, ScheduleGenerateResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=List[Schedule])
|
||||
async def get_schedules(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=100),
|
||||
schedule_date: date = Query(None),
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить список расписаний пользователя"""
|
||||
if schedule_date:
|
||||
schedule = await crud_schedule.get_by_date(db, current_user.id, schedule_date)
|
||||
return [schedule] if schedule else []
|
||||
else:
|
||||
schedules = await crud_schedule.get_by_user(db, current_user.id, skip, limit)
|
||||
return schedules
|
||||
|
||||
|
||||
@router.get("/{schedule_id}", response_model=Schedule)
|
||||
async def get_schedule(
|
||||
schedule_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить расписание по ID"""
|
||||
schedule = await crud_schedule.get_with_tasks(db, schedule_id)
|
||||
if not schedule:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Schedule not found"
|
||||
)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
return schedule
|
||||
|
||||
|
||||
@router.post("", response_model=Schedule, status_code=status.HTTP_201_CREATED)
|
||||
async def create_schedule(
|
||||
schedule_in: ScheduleCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Создать новое расписание"""
|
||||
schedule_data = schedule_in.model_dump()
|
||||
schedule_data["user_id"] = current_user.id
|
||||
schedule = await crud_schedule.create(db, schedule_data)
|
||||
return schedule
|
||||
|
||||
|
||||
@router.put("/{schedule_id}", response_model=Schedule)
|
||||
async def update_schedule(
|
||||
schedule_id: str,
|
||||
schedule_in: ScheduleUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Обновить расписание"""
|
||||
schedule = await crud_schedule.get(db, schedule_id)
|
||||
if not schedule:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Schedule not found"
|
||||
)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
update_data = schedule_in.model_dump(exclude_unset=True)
|
||||
schedule = await crud_schedule.update(db, schedule, update_data)
|
||||
return schedule
|
||||
|
||||
|
||||
@router.delete("/{schedule_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_schedule(
|
||||
schedule_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Удалить расписание"""
|
||||
schedule = await crud_schedule.get(db, schedule_id)
|
||||
if not schedule:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Schedule not found"
|
||||
)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
await crud_schedule.delete(db, schedule_id)
|
||||
return None
|
||||
|
||||
|
||||
@router.post("/generate", response_model=ScheduleGenerateResponse)
|
||||
async def generate_schedule(
|
||||
request: ScheduleGenerateRequest,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Сгенерировать расписание через ИИ"""
|
||||
try:
|
||||
result = await schedule_generator.generate(
|
||||
db=db,
|
||||
user_id=current_user.id,
|
||||
child_age=request.child_age,
|
||||
preferences=request.preferences,
|
||||
schedule_date=request.date,
|
||||
description=request.description
|
||||
)
|
||||
return ScheduleGenerateResponse(
|
||||
schedule_id=result["schedule_id"],
|
||||
title=result["title"],
|
||||
tasks=result["tasks"]
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to generate schedule: {str(e)}"
|
||||
)
|
||||
|
||||
165
new-planet-backend/app/api/v1/tasks.py
Normal file
165
new-planet-backend/app/api/v1/tasks.py
Normal file
@@ -0,0 +1,165 @@
|
||||
from typing import List
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.session import get_db
|
||||
from app.api.deps import get_current_active_user
|
||||
from app.models.user import User
|
||||
from app.schemas.task import Task, TaskCreate, TaskUpdate
|
||||
from app.crud import task as crud_task, schedule as crud_schedule
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/schedule/{schedule_id}", response_model=List[Task])
|
||||
async def get_tasks(
|
||||
schedule_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить все задачи расписания"""
|
||||
# Проверка прав доступа
|
||||
schedule = await crud_schedule.get(db, schedule_id)
|
||||
if not schedule:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Schedule not found"
|
||||
)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
tasks = await crud_task.get_by_schedule(db, schedule_id)
|
||||
return tasks
|
||||
|
||||
|
||||
@router.get("/{task_id}", response_model=Task)
|
||||
async def get_task(
|
||||
task_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Получить задачу по ID"""
|
||||
task = await crud_task.get(db, task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
# Проверка прав доступа через расписание
|
||||
schedule = await crud_schedule.get(db, task.schedule_id)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
return task
|
||||
|
||||
|
||||
@router.post("", response_model=Task, status_code=status.HTTP_201_CREATED)
|
||||
async def create_task(
|
||||
task_in: TaskCreate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Создать новую задачу"""
|
||||
# Проверка прав доступа
|
||||
schedule = await crud_schedule.get(db, task_in.schedule_id)
|
||||
if not schedule:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Schedule not found"
|
||||
)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
task = await crud_task.create(db, task_in.model_dump())
|
||||
return task
|
||||
|
||||
|
||||
@router.put("/{task_id}", response_model=Task)
|
||||
async def update_task(
|
||||
task_id: str,
|
||||
task_in: TaskUpdate,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Обновить задачу"""
|
||||
task = await crud_task.get(db, task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
# Проверка прав доступа
|
||||
schedule = await crud_schedule.get(db, task.schedule_id)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
update_data = task_in.model_dump(exclude_unset=True)
|
||||
task = await crud_task.update(db, task, update_data)
|
||||
return task
|
||||
|
||||
|
||||
@router.patch("/{task_id}/complete", response_model=Task)
|
||||
async def complete_task(
|
||||
task_id: str,
|
||||
completed: bool = True,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Отметить задачу как выполненную/невыполненную"""
|
||||
task = await crud_task.get(db, task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
# Проверка прав доступа
|
||||
schedule = await crud_schedule.get(db, task.schedule_id)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
task = await crud_task.update_completion(db, task_id, completed)
|
||||
return task
|
||||
|
||||
|
||||
@router.delete("/{task_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_task(
|
||||
task_id: str,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Удалить задачу"""
|
||||
task = await crud_task.get(db, task_id)
|
||||
if not task:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Task not found"
|
||||
)
|
||||
|
||||
# Проверка прав доступа
|
||||
schedule = await crud_schedule.get(db, task.schedule_id)
|
||||
if schedule.user_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
|
||||
await crud_task.delete(db, task_id)
|
||||
return None
|
||||
|
||||
109
new-planet-backend/app/api/v1/websocket.py
Normal file
109
new-planet-backend/app/api/v1/websocket.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends
|
||||
from typing import Dict, Set, Optional
|
||||
import json
|
||||
from app.services.chat_service import chat_service
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.api.deps import get_current_user
|
||||
from app.core.security import decode_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Хранилище активных соединений
|
||||
active_connections: Dict[str, Set[WebSocket]] = {}
|
||||
|
||||
|
||||
class ConnectionManager:
|
||||
def __init__(self):
|
||||
self.active_connections: Dict[str, Set[WebSocket]] = {}
|
||||
|
||||
async def connect(self, websocket: WebSocket, user_id: str):
|
||||
await websocket.accept()
|
||||
if user_id not in self.active_connections:
|
||||
self.active_connections[user_id] = set()
|
||||
self.active_connections[user_id].add(websocket)
|
||||
|
||||
def disconnect(self, websocket: WebSocket, user_id: str):
|
||||
if user_id in self.active_connections:
|
||||
self.active_connections[user_id].discard(websocket)
|
||||
if not self.active_connections[user_id]:
|
||||
del self.active_connections[user_id]
|
||||
|
||||
async def send_personal_message(self, message: str, websocket: WebSocket):
|
||||
await websocket.send_text(message)
|
||||
|
||||
async def broadcast_to_user(self, user_id: str, message: str):
|
||||
if user_id in self.active_connections:
|
||||
for connection in self.active_connections[user_id]:
|
||||
await connection.send_text(message)
|
||||
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
async def get_user_from_token(token: str) -> Optional[str]:
|
||||
"""Получить user_id из токена"""
|
||||
payload = decode_token(token)
|
||||
if payload:
|
||||
return payload.get("sub")
|
||||
return None
|
||||
|
||||
|
||||
@router.websocket("/ws/chat")
|
||||
async def websocket_chat(websocket: WebSocket):
|
||||
"""WebSocket endpoint для чата с ИИ"""
|
||||
# Получаем токен из query параметров
|
||||
token = websocket.query_params.get("token")
|
||||
if not token:
|
||||
await websocket.close(code=1008, reason="Token required")
|
||||
return
|
||||
|
||||
# Проверка токена
|
||||
user_id = await get_user_from_token(token)
|
||||
if not user_id:
|
||||
await websocket.close(code=1008, reason="Unauthorized")
|
||||
return
|
||||
|
||||
await manager.connect(websocket, user_id)
|
||||
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
while True:
|
||||
data = await websocket.receive_text()
|
||||
message_data = json.loads(data)
|
||||
|
||||
# Создаем запрос для chat_service
|
||||
from app.schemas.ai import ChatRequest
|
||||
request = ChatRequest(
|
||||
message=message_data.get("message", ""),
|
||||
conversation_id=message_data.get("conversation_id")
|
||||
)
|
||||
|
||||
# Получаем ответ от ИИ
|
||||
try:
|
||||
response = await chat_service.chat(
|
||||
db=db,
|
||||
user_id=user_id,
|
||||
request=request
|
||||
)
|
||||
|
||||
# Отправляем ответ клиенту
|
||||
await manager.send_personal_message(
|
||||
json.dumps({
|
||||
"type": "message",
|
||||
"response": response.response,
|
||||
"conversation_id": response.conversation_id,
|
||||
"tokens_used": response.tokens_used
|
||||
}),
|
||||
websocket
|
||||
)
|
||||
except Exception as e:
|
||||
await manager.send_personal_message(
|
||||
json.dumps({
|
||||
"type": "error",
|
||||
"message": str(e)
|
||||
}),
|
||||
websocket
|
||||
)
|
||||
except WebSocketDisconnect:
|
||||
manager.disconnect(websocket, user_id)
|
||||
|
||||
Reference in New Issue
Block a user