from datetime import datetime from uuid import UUID from sqlalchemy import Boolean, DateTime, Integer, String, Text from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) keycloak_id: Mapped[UUID] = mapped_column( PGUUID(as_uuid=True), unique=True, index=True ) username: Mapped[str] = mapped_column(String(50), unique=True) email: Mapped[str] = mapped_column(String(100), unique=True) role: Mapped[str] = mapped_column(String(20), default="user") is_active: Mapped[bool] = mapped_column(Boolean, default=True) is_superuser: Mapped[bool] = mapped_column(Boolean, default=False) attributes: Mapped[dict | None] = mapped_column(JSONB, nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow ) last_login_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) class Project(Base): __tablename__ = "projects" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) name: Mapped[str] = mapped_column(String(100)) code: Mapped[str] = mapped_column(String(50), unique=True) description: Mapped[str | None] = mapped_column(Text, nullable=True) gs_workspace: Mapped[str] = mapped_column(String(100), unique=True) status: Mapped[str] = mapped_column(String(20), default="active") created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow ) class ProjectDatabase(Base): __tablename__ = "project_databases" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) project_id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), index=True) db_role: Mapped[str] = mapped_column(String(20)) db_type: Mapped[str] = mapped_column(String(20)) dsn_encrypted: Mapped[str] = mapped_column(Text) pool_min_size: Mapped[int] = mapped_column(Integer, default=2) pool_max_size: Mapped[int] = mapped_column(Integer, default=10) class ProjectGeoServerConfig(Base): __tablename__ = "project_geoserver_configs" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) project_id: Mapped[UUID] = mapped_column( PGUUID(as_uuid=True), unique=True, index=True ) gs_base_url: Mapped[str | None] = mapped_column(Text, nullable=True) gs_admin_user: Mapped[str | None] = mapped_column(String(50), nullable=True) gs_admin_password_encrypted: Mapped[str | None] = mapped_column( Text, nullable=True ) gs_datastore_name: Mapped[str] = mapped_column(String(100), default="ds_postgis") default_extent: Mapped[dict | None] = mapped_column(JSONB, nullable=True) srid: Mapped[int] = mapped_column(Integer, default=4326) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow ) class UserProjectMembership(Base): __tablename__ = "user_project_membership" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) user_id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), index=True) project_id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), index=True) project_role: Mapped[str] = mapped_column(String(20), default="viewer") class AuditLog(Base): __tablename__ = "audit_logs" id: Mapped[UUID] = mapped_column(PGUUID(as_uuid=True), primary_key=True) user_id: Mapped[UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True, index=True ) project_id: Mapped[UUID | None] = mapped_column( PGUUID(as_uuid=True), nullable=True, index=True ) action: Mapped[str] = mapped_column(String(50)) resource_type: Mapped[str | None] = mapped_column(String(50), nullable=True) resource_id: Mapped[str | None] = mapped_column(String(100), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) request_method: Mapped[str | None] = mapped_column(String(10), nullable=True) request_path: Mapped[str | None] = mapped_column(Text, nullable=True) request_data: Mapped[dict | None] = mapped_column(JSONB, nullable=True) response_status: Mapped[int | None] = mapped_column(Integer, nullable=True) timestamp: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow )