Files
ruslan efa1c26e5d Email improvements: domain-aware portal URL, embedded logo, fix product list color
- Store request origin domain in PendingAccessRequest.portal_url
- Use per-request portal URL in approval/rejection emails
- Embed logo as base64 so it displays without external image loading
- Fix 'Предоставлен доступ к продуктам' text color to match body color
- Switch Telegram polling to 30-second interval with single-worker flock fix
2026-05-29 16:10:40 +00:00

133 lines
6.1 KiB
Python

import datetime as dt
import enum
from typing import Optional
from sqlalchemy import (
Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column
from database import Base
class ServiceType(str, enum.Enum):
WEB = "WEB"
VNC = "VNC"
RDP = "RDP"
class SessionStatus(str, enum.Enum):
ACTIVE = "ACTIVE"
EXPIRED = "EXPIRED"
TERMINATED = "TERMINATED"
ROTATED = "ROTATED"
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(255))
expires_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), index=True)
active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
first_name: Mapped[str] = mapped_column(String(64), default="")
last_name: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class Service(Base):
__tablename__ = "services"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String(128))
slug: Mapped[str] = mapped_column(String(64), unique=True, index=True)
type: Mapped[ServiceType] = mapped_column(Enum(ServiceType), index=True)
target: Mapped[str] = mapped_column(Text)
comment: Mapped[str] = mapped_column(Text, default="")
svc_login: Mapped[str] = mapped_column(String(256), default="")
svc_password: Mapped[str] = mapped_column(String(256), default="")
svc_cred_hint: Mapped[str] = mapped_column(Text, default="")
icon_path: Mapped[str] = mapped_column(Text, default="")
active: Mapped[bool] = mapped_column(Boolean, default=True)
warm_pool_size: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class Category(Base):
__tablename__ = "categories"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String(128), unique=True, index=True)
slug: Mapped[str] = mapped_column(String(64), unique=True, index=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class ServiceCategory(Base):
__tablename__ = "service_categories"
__table_args__ = (UniqueConstraint("service_id", "category_id", name="uq_service_category"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
service_id: Mapped[int] = mapped_column(ForeignKey("services.id", ondelete="CASCADE"), index=True)
category_id: Mapped[int] = mapped_column(ForeignKey("categories.id", ondelete="CASCADE"), index=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class UserServiceAccess(Base):
__tablename__ = "user_service_access"
__table_args__ = (UniqueConstraint("user_id", "service_id", name="uq_user_service"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
service_id: Mapped[int] = mapped_column(ForeignKey("services.id", ondelete="CASCADE"), index=True)
granted_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class RdpSlot(Base):
__tablename__ = "rdp_slots"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
service_id: Mapped[int] = mapped_column(ForeignKey("services.id", ondelete="CASCADE"), index=True)
rdp_username: Mapped[str] = mapped_column(String(128))
rdp_password: Mapped[str] = mapped_column(String(256), default="")
container_name: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))
class SessionModel(Base):
__tablename__ = "sessions"
id: Mapped[str] = mapped_column(String(36), primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True)
service_id: Mapped[int] = mapped_column(ForeignKey("services.id", ondelete="CASCADE"), index=True)
status: Mapped[SessionStatus] = mapped_column(Enum(SessionStatus), default=SessionStatus.ACTIVE, index=True)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc), index=True)
last_access_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc), index=True)
container_id: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, index=True)
action: Mapped[str] = mapped_column(String(128), index=True)
details: Mapped[str] = mapped_column(Text)
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc), index=True)
class PendingAccessRequest(Base):
__tablename__ = "pending_access_requests"
id: Mapped[str] = mapped_column(String(12), primary_key=True)
name: Mapped[str] = mapped_column(String(256))
company: Mapped[str] = mapped_column(String(256))
email: Mapped[str] = mapped_column(String(256))
phone: Mapped[str] = mapped_column(String(64))
manager: Mapped[str] = mapped_column(String(256), default="")
products_json: Mapped[str] = mapped_column(Text, default="[]")
portal_url: Mapped[str] = mapped_column(String(256), default="")
status: Mapped[str] = mapped_column(String(16), default="pending")
created_at: Mapped[dt.datetime] = mapped_column(DateTime(timezone=True), default=lambda: dt.datetime.now(dt.timezone.utc))