chore: add CI, lint config, login rate limit and split large modules

- Rate-limit failed logins (8 per 15 min per IP or username, 429 + Retry-After)
- Add ruff/mypy config and GitHub Actions workflow (ruff, pytest, tsc, tests, build)
- Apply ruff autofixes (import order, pyupgrade)
- Extract rpy_helpers.py, engine-util.ts, TranslationParts.tsx and
  translation-types.ts from oversized modules
- Add dependency-free React UI tests
- Refresh README and ARCHITECTURE (Naninovel/Tomie, status and plans)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Sonnet 5.5 committed 2026-10-25 11:23:22 +03:00
1 parent 57a4edcbfd
commit bbbcc6a5d8
42 files changed
+678 -391

No files matched your search

+12 -2
View File
@@ -1,9 +1,10 @@
from fastapi import APIRouter, HTTPException, Response, status
from fastapi import APIRouter, HTTPException, Request, Response, status
from sqlalchemy import select
from app.config import get_settings
from app.deps import DB, SessionUser
from app.models import User
from app.ratelimit import client_ip, login_limiter
from app.schemas import ChangeCredentialsIn, LoginIn, MeOut
from app.security import (
COOKIE_NAME,
@@ -30,10 +31,19 @@ def _set_cookie(response: Response, user: User) -> None:
@router.post("/login", response_model=MeOut)
async def login(body: LoginIn, response: Response, db: DB) -> User:
async def login(body: LoginIn, request: Request, response: Response, db: DB) -> User:
keys = (f"ip:{client_ip(request)}", f"user:{body.username.lower()}")
if wait := login_limiter.retry_after(*keys):
raise HTTPException(
status.HTTP_429_TOO_MANY_REQUESTS,
f"Слишком много неудачных попыток входа. Повторите через {wait} с",
headers={"Retry-After": str(wait)},
)
user = await db.scalar(select(User).where(User.username == body.username))
if not user or not user.is_active or not verify_password(user.password_hash, body.password):
login_limiter.fail(*keys)
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Неверный логин или пароль")
login_limiter.reset(*keys)
_set_cookie(response, user)
return user