feat: FASE 1 - Backend Core (modelli, auth, API)

Implementazione completa del backend FastAPI:
- Modelli SQLAlchemy: User, Recipe, RecipeVersion, RecipeTask,
  RecipeSubtask, Measurement, AccessLog, SystemSetting, RecipeVersionAudit
- Schemas Pydantic v2 per tutti i CRUD + statistiche SPC
- Middleware: API Key auth (X-API-Key) con role checking + access logging
- Router: auth, users, recipes, tasks, measurements, files, settings
- Services: auth (bcrypt+secrets), recipe (copy-on-write versioning),
  measurement (auto pass/fail con UTL/UWL/LWL/LTL)
- Alembic env.py con import modelli attivi
- Fix architect review: no double-commit, recipe_id subquery filter,
  user_id in access logs, type annotations corrette

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-07 00:40:50 +01:00
parent 76be6f5ac4
commit d6508e0ae8
28 changed files with 2942 additions and 7 deletions
+32
View File
@@ -0,0 +1,32 @@
"""Access log model for tracking user actions."""
from datetime import datetime
from typing import Optional
from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, Integer, JSON, String, func
from sqlalchemy.orm import Mapped, mapped_column
from database import Base
class AccessLog(Base):
__tablename__ = "access_logs"
id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
user_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("users.id"), nullable=True
)
action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
details: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
ip_address: Mapped[Optional[str]] = mapped_column(String(45), nullable=True)
user_agent: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime, nullable=False, server_default=func.now()
)
__table_args__ = (
Index("ix_access_log_user_time", "user_id", "created_at"),
{"mysql_engine": "InnoDB", "mysql_charset": "utf8mb4"},
)
def __repr__(self) -> str:
return f"<AccessLog user={self.user_id} action={self.action}>"