From dd91f6b10c2f95704a2af2db55d1d1a45c8ea12e Mon Sep 17 00:00:00 2001 From: Agzamus Date: Mon, 12 Oct 2026 16:00:58 +0300 Subject: [PATCH] =?UTF-8?q?feat:=203.3-5=20=D0=9E=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/__init__.py | 5 +++++ app/models/audit_event.py | 35 +++++++++++++++++++++++++++++++++++ app/models/oauth_client.py | 31 +++++++++++++++++++++++++++++++ app/models/user.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 app/models/__init__.py create mode 100644 app/models/audit_event.py create mode 100644 app/models/oauth_client.py create mode 100644 app/models/user.py diff --git a/app/models/__init__.py b/app/models/__init__.py new file mode 100644 index 0000000..b1b08fa --- /dev/null +++ b/app/models/__init__.py @@ -0,0 +1,5 @@ +from app.models.audit_event import AuditEvent +from app.models.oauth_client import OAuthClient +from app.models.user import User + +__all__ = ["AuditEvent", "OAuthClient", "User"] diff --git a/app/models/audit_event.py b/app/models/audit_event.py new file mode 100644 index 0000000..d31f288 --- /dev/null +++ b/app/models/audit_event.py @@ -0,0 +1,35 @@ +from datetime import datetime +from typing import Any + +from sqlalchemy import JSON, DateTime, ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base +from app.models.user import utcnow + + +class AuditEvent(Base): + __tablename__ = "audit_events" + + id: Mapped[int] = mapped_column(primary_key=True) + + # строковый тип события (индексируется); + event_type: Mapped[str] = mapped_column(String(100), index=True) + + # внешний ключ на `users.id`, может быть NULL для анонимных событий; + actor_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True) + + # строковый идентификатор клиента (если применимо); + client_id: Mapped[str | None] = mapped_column(String(64), nullable=True) + + # откуда пришло событие; + ip: Mapped[str | None] = mapped_column(String(45), nullable=True) + user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True) + + # JSON-поле для произвольных данных; + metadata_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) + + # created_at + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utcnow + ) diff --git a/app/models/oauth_client.py b/app/models/oauth_client.py new file mode 100644 index 0000000..a98fffa --- /dev/null +++ b/app/models/oauth_client.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from sqlalchemy import Boolean, DateTime, JSON, String +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base +from app.models.user import utcnow + + +class OAuthClient(Base): + __tablename__ = "oauth_clients" + + # id + id: Mapped[int] = mapped_column(primary_key=True) + + # уникальный строковый идентификатор; + client_id: Mapped[str] = mapped_column(String(64), unique=True, index=True) + + # читаемое имя приложения; + name: Mapped[str] = mapped_column(String(255)) + + # флаг «конфиденциальный / публичный клиент»; + is_confidential: Mapped[bool] = mapped_column(Boolean, default=True) + + # список разрешённых URL (JSON-поле); + redirect_uris: Mapped[list[str]] = mapped_column(JSON, default=list) + + # created_at + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utcnow + ) diff --git a/app/models/user.py b/app/models/user.py new file mode 100644 index 0000000..a333478 --- /dev/null +++ b/app/models/user.py @@ -0,0 +1,32 @@ +from datetime import datetime, timezone + +from sqlalchemy import Boolean, DateTime, String +from sqlalchemy.orm import Mapped, mapped_column + +from app.db.base import Base + + +def utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class User(Base): + __tablename__ = "users" + + # первичный ключ, автоинкремент; + id: Mapped[int] = mapped_column(primary_key=True) + + # уникальное, индексируемое поле; + email: Mapped[str] = mapped_column(String(255), unique=True, index=True) + + # строка; + hashed_password: Mapped[str] = mapped_column(String(255)) + + # булевы флаги; + is_active: Mapped[bool] = mapped_column(Boolean, default=True) + is_admin: Mapped[bool] = mapped_column(Boolean, default=False) + + # дата-время создания записи. + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utcnow + )