Split backend into multiple files

This commit is contained in:
2025-08-17 19:27:47 +03:00
Unverified
parent 1156345feb
commit cc819257a3
7 changed files with 187 additions and 167 deletions
+19
View File
@@ -0,0 +1,19 @@
from fastapi import FastAPI
from fastapi.security import HTTPBearer
from fastapi.middleware.cors import CORSMiddleware
# Инициализация FastAPI
app = FastAPI(title="PixelChat")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # В продакшене замените на нужные домены
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Security для FastAPI
security = HTTPBearer()
+6
View File
@@ -0,0 +1,6 @@
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from constants import DATABASE_URL
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+39
View File
@@ -0,0 +1,39 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from utils import *
from models import *
from db import SessionLocal
from app import security
# Зависимость для получения текущего пользователя
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(lambda: SessionLocal())
):
token = credentials.credentials
payload = verify_token(token)
if not payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
user = db.query(User).filter(User.id == payload["user_id"]).first()
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
headers={"WWW-Authenticate": "Bearer"},
)
return {"user_id": user.id, "username": user.username}
# Зависимость для получения сессии БД
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
+9 -167
View File
@@ -1,172 +1,14 @@
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy import create_engine, Column, Integer, String, Text, Boolean, DateTime, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, Session
from datetime import datetime, timedelta
import jwt
import re
from typing import Optional
from pydantic import BaseModel
import bcrypt
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from datetime import datetime
from constants import *
# Инициализация FastAPI
app = FastAPI(title="PixelChat")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # В продакшене замените на нужные домены
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Настройка базы данных
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# Модели базы данных
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False, index=True)
password_hash = Column(String(200), nullable=False)
online = Column(Boolean, default=False)
last_seen = Column(DateTime, default=datetime.now)
created_at = Column(DateTime, default=datetime.now)
messages = relationship("Message", back_populates="author", lazy="select")
class Message(Base):
__tablename__ = "message"
id = Column(Integer, primary_key=True, index=True)
content = Column(Text, nullable=False)
timestamp = Column(DateTime, default=datetime.now)
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
is_read = Column(Boolean, default=False)
author = relationship("User", back_populates="messages")
# Валидация данных
def is_valid_username(username: str) -> bool:
if len(username) < 3 or len(username) > 20:
return False
if re.search(r'[\s\u180E\u200B-\u200D\u2060\uFEFF]', username):
return False
return True
def is_valid_password(password: str) -> bool:
if len(password) < 5 or len(password) > 50:
return False
if re.search(r'[\s\u180E\u200B-\u200D\u2060\uFEFF]', password):
return False
return True
# JWT Helper Functions
def create_token(user_id: int, username: str) -> str:
expire = datetime.utcnow() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
payload = {
"user_id": user_id,
"username": username,
"exp": expire
}
return jwt.encode(payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
def verify_token(token: str) -> Optional[dict]:
try:
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
def get_password_hash(password: str) -> str:
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
# Security для FastAPI
security = HTTPBearer()
# Зависимость для получения текущего пользователя
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(lambda: SessionLocal())
):
token = credentials.credentials
payload = verify_token(token)
if not payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
user = db.query(User).filter(User.id == payload["user_id"]).first()
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="User not found",
headers={"WWW-Authenticate": "Bearer"},
)
return {"user_id": user.id, "username": user.username}
# Зависимость для получения сессии БД
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Pydantic модели
class LoginRequest(BaseModel):
username: str
password: str
class RegisterRequest(BaseModel):
username: str
password: str
confirm_password: str
class SendMessageRequest(BaseModel):
content: str
class MessageResponse(BaseModel):
id: int
content: str
timestamp: datetime
is_author: bool
is_read: bool
username: str
class Config:
from_attributes = True
# Создание таблиц
Base.metadata.create_all(bind=engine)
from db import *
from models import *
from validation import *
from utils import *
from dependencies import *
from app import *
# API маршруты
+65
View File
@@ -0,0 +1,65 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
from db import engine
from pydantic import BaseModel
Base = declarative_base()
# Модели базы данных
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False, index=True)
password_hash = Column(String(200), nullable=False)
online = Column(Boolean, default=False)
last_seen = Column(DateTime, default=datetime.now)
created_at = Column(DateTime, default=datetime.now)
messages = relationship("Message", back_populates="author", lazy="select")
class Message(Base):
__tablename__ = "message"
id = Column(Integer, primary_key=True, index=True)
content = Column(Text, nullable=False)
timestamp = Column(DateTime, default=datetime.now)
user_id = Column(Integer, ForeignKey("user.id"), nullable=False)
is_read = Column(Boolean, default=False)
author = relationship("User", back_populates="messages")
# Pydantic модели
class LoginRequest(BaseModel):
username: str
password: str
class RegisterRequest(BaseModel):
username: str
password: str
confirm_password: str
class SendMessageRequest(BaseModel):
content: str
class MessageResponse(BaseModel):
id: int
content: str
timestamp: datetime
is_author: bool
is_read: bool
username: str
class Config:
from_attributes = True
# Создание таблиц
Base.metadata.create_all(bind=engine)
+33
View File
@@ -0,0 +1,33 @@
from datetime import datetime, timedelta
import jwt
from typing import Optional
import bcrypt
from constants import *
# JWT Helper Functions
def create_token(user_id: int, username: str) -> str:
expire = datetime.now() + timedelta(hours=ACCESS_TOKEN_EXPIRE_HOURS)
payload = {
"user_id": user_id,
"username": username,
"exp": expire
}
return jwt.encode(payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM)
def verify_token(token: str) -> Optional[dict]:
try:
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(plain_password.encode("utf-8"), hashed_password.encode("utf-8"))
def get_password_hash(password: str) -> str:
return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
+16
View File
@@ -0,0 +1,16 @@
import re
def is_valid_username(username: str) -> bool:
if len(username) < 3 or len(username) > 20:
return False
if re.search(r'[\s\u180E\u200B-\u200D\u2060\uFEFF]', username):
return False
return True
def is_valid_password(password: str) -> bool:
if len(password) < 5 or len(password) > 50:
return False
if re.search(r'[\s\u180E\u200B-\u200D\u2060\uFEFF]', password):
return False
return True