From 61249cc3dbd92bb78e65f21014bd6f5ca6c0e064 Mon Sep 17 00:00:00 2001 From: Polina Date: Thu, 2 Jul 2026 17:06:58 +0000 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?app/routers=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routers/__init__.py | Bin 0 -> 1024 bytes app/routers/api.py | 97 ++++++++++++++++++++++++++++ app/routers/web.py | 139 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 app/routers/__init__.py create mode 100644 app/routers/api.py create mode 100644 app/routers/web.py diff --git a/app/routers/__init__.py b/app/routers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..06d7405020018ddf3cacee90fd4af10487da3d20 GIT binary patch literal 1024 ScmZQz7zLvtFd70QH3R?z00031 literal 0 HcmV?d00001 diff --git a/app/routers/api.py b/app/routers/api.py new file mode 100644 index 0000000..cc53f04 --- /dev/null +++ b/app/routers/api.py @@ -0,0 +1,97 @@ +import json + +from fastapi import APIRouter, Depends, HTTPException, Request, status +from sqlalchemy.orm import Session as DbSession + +from app.auth import verify_api_key +from app.database import get_db +from app.models import ExternalData, Session, User +from app.schemas import ExternalDataCreate, ExternalDataPublic, SessionPublic, UserPublic + +router = APIRouter(prefix="/api/v1", tags=["external-api"]) + + +@router.get("/users/{user_id}", response_model=UserPublic) +def get_user( + user_id: int, + _: None = Depends(verify_api_key), + db: DbSession = Depends(get_db), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + return user + + +@router.get("/users/by-email/{email}", response_model=UserPublic) +def get_user_by_email( + email: str, + _: None = Depends(verify_api_key), + db: DbSession = Depends(get_db), +): + user = db.query(User).filter(User.email == email.strip().lower()).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + return user + + +@router.get("/users/{user_id}/sessions", response_model=list[SessionPublic]) +def get_user_sessions( + user_id: int, + active_only: bool = True, + _: None = Depends(verify_api_key), + db: DbSession = Depends(get_db), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + query = db.query(Session).filter(Session.user_id == user_id) + if active_only: + query = query.filter(Session.is_active.is_(True)) + return query.order_by(Session.created_at.desc()).all() + + +@router.post("/users/{user_id}/data", response_model=ExternalDataPublic, status_code=status.HTTP_201_CREATED) +def write_external_data( + user_id: int, + body: ExternalDataCreate, + _: None = Depends(verify_api_key), + db: DbSession = Depends(get_db), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + + payload_str = body.payload if isinstance(body.payload, str) else json.dumps(body.payload, ensure_ascii=False) + + record = ExternalData(user_id=user_id, source=body.source, payload=payload_str) + db.add(record) + + if body.points_delta: + user.points = max(0, user.points + body.points_delta) + if body.rating_delta: + user.rating = max(0.0, user.rating + body.rating_delta) + + db.commit() + db.refresh(record) + return record + + +@router.get("/users/{user_id}/data", response_model=list[ExternalDataPublic]) +def get_external_data( + user_id: int, + limit: int = 50, + _: None = Depends(verify_api_key), + db: DbSession = Depends(get_db), +): + user = db.query(User).filter(User.id == user_id).first() + if not user: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found") + limit = min(max(limit, 1), 100) + return ( + db.query(ExternalData) + .filter(ExternalData.user_id == user_id) + .order_by(ExternalData.created_at.desc()) + .limit(limit) + .all() + ) diff --git a/app/routers/web.py b/app/routers/web.py new file mode 100644 index 0000000..3eaff1a --- /dev/null +++ b/app/routers/web.py @@ -0,0 +1,139 @@ +from fastapi import APIRouter, Depends, Form, HTTPException, Request, status +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.templating import Jinja2Templates +from sqlalchemy.orm import Session as DbSession + +from app.auth import ( + SESSION_COOKIE, + create_user_session, + get_current_user, + get_current_user_optional, + hash_password, + verify_password, +) +from app.database import get_db +from app.models import Session, User + +router = APIRouter(tags=["web"]) +templates = Jinja2Templates(directory="templates") + + +@router.get("/", response_class=HTMLResponse) +def index(request: Request, user: User | None = Depends(get_current_user_optional)): + if user: + return RedirectResponse("/cabinet", status_code=status.HTTP_302_FOUND) + return RedirectResponse("/login", status_code=status.HTTP_302_FOUND) + + +@router.get("/register", response_class=HTMLResponse) +def register_page(request: Request, user: User | None = Depends(get_current_user_optional)): + if user: + return RedirectResponse("/cabinet", status_code=status.HTTP_302_FOUND) + return templates.TemplateResponse(request, "register.html", {"error": None}) + + +@router.post("/register") +def register( + request: Request, + email: str = Form(...), + password: str = Form(...), + db: DbSession = Depends(get_db), +): + email = email.strip().lower() + if len(password) < 6: + return templates.TemplateResponse( + request, + "register.html", + {"error": "Пароль должен быть не менее 6 символов"}, + status_code=400, + ) + existing = db.query(User).filter(User.email == email).first() + if existing: + return templates.TemplateResponse( + request, + "register.html", + {"error": "Пользователь с такой почтой уже существует"}, + status_code=400, + ) + user = User(email=email, password_hash=hash_password(password)) + db.add(user) + db.commit() + db.refresh(user) + session = create_user_session(db, user) + response = RedirectResponse("/cabinet", status_code=status.HTTP_302_FOUND) + response.set_cookie( + key=SESSION_COOKIE, + value=session.token, + httponly=True, + samesite="lax", + max_age=86400, + ) + return response + + +@router.get("/login", response_class=HTMLResponse) +def login_page(request: Request, user: User | None = Depends(get_current_user_optional)): + if user: + return RedirectResponse("/cabinet", status_code=status.HTTP_302_FOUND) + return templates.TemplateResponse(request, "login.html", {"error": None}) + + +@router.post("/login") +def login( + request: Request, + email: str = Form(...), + password: str = Form(...), + db: DbSession = Depends(get_db), +): + email = email.strip().lower() + user = db.query(User).filter(User.email == email).first() + if not user or not verify_password(password, user.password_hash): + return templates.TemplateResponse( + request, + "login.html", + {"error": "Неверная почта или пароль"}, + status_code=400, + ) + session = create_user_session(db, user) + response = RedirectResponse("/cabinet", status_code=status.HTTP_302_FOUND) + response.set_cookie( + key=SESSION_COOKIE, + value=session.token, + httponly=True, + samesite="lax", + max_age=86400, + ) + return response + + +@router.post("/logout") +def logout(request: Request, db: DbSession = Depends(get_db)): + token = request.cookies.get(SESSION_COOKIE) + if token: + session = db.query(Session).filter(Session.token == token).first() + if session: + session.is_active = False + db.commit() + response = RedirectResponse("/login", status_code=status.HTTP_302_FOUND) + response.delete_cookie(SESSION_COOKIE) + return response + + +@router.get("/cabinet", response_class=HTMLResponse) +def cabinet(request: Request, user: User = Depends(get_current_user), db: DbSession = Depends(get_db)): + sessions = ( + db.query(Session) + .filter(Session.user_id == user.id, Session.is_active.is_(True)) + .order_by(Session.created_at.desc()) + .all() + ) + external_data = list(reversed(user.external_data[-10:])) if user.external_data else [] + return templates.TemplateResponse( + request, + "cabinet.html", + { + "user": user, + "sessions": sessions, + "external_data": external_data, + }, + )