from __future__ import annotations from datetime import datetime from enum import Enum from typing import Optional from sqlalchemy import ( DateTime, Enum as SAEnum, ForeignKey, Index, JSON, String, Text, UniqueConstraint, text, ) from sqlalchemy.dialects.mysql import BIGINT, INTEGER, TINYINT from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): """Base declarative class for all models.""" # Enumerations centralized to avoid magic strings across the codebase. class CredentialType(str, Enum): ACCESS_KEY = "ACCESS_KEY" ASSUME_ROLE = "ASSUME_ROLE" class RoleName(str, Enum): ADMIN = "ADMIN" CUSTOMER_ADMIN = "CUSTOMER_ADMIN" CUSTOMER_USER = "CUSTOMER_USER" class InstanceStatus(str, Enum): PENDING = "PENDING" RUNNING = "RUNNING" STOPPING = "STOPPING" STOPPED = "STOPPED" SHUTTING_DOWN = "SHUTTING_DOWN" TERMINATED = "TERMINATED" UNKNOWN = "UNKNOWN" class InstanceDesiredStatus(str, Enum): RUNNING = "RUNNING" STOPPED = "STOPPED" TERMINATED = "TERMINATED" class JobType(str, Enum): SYNC_INSTANCES = "SYNC_INSTANCES" START_INSTANCES = "START_INSTANCES" STOP_INSTANCES = "STOP_INSTANCES" REBOOT_INSTANCES = "REBOOT_INSTANCES" TERMINATE_INSTANCES = "TERMINATE_INSTANCES" CREATE_INSTANCES = "CREATE_INSTANCES" class JobStatus(str, Enum): PENDING = "PENDING" RUNNING = "RUNNING" SUCCESS = "SUCCESS" FAILED = "FAILED" class JobItemResourceType(str, Enum): INSTANCE = "INSTANCE" OTHER = "OTHER" class JobItemAction(str, Enum): CREATE = "CREATE" START = "START" STOP = "STOP" REBOOT = "REBOOT" TERMINATE = "TERMINATE" SYNC = "SYNC" class JobItemStatus(str, Enum): PENDING = "PENDING" RUNNING = "RUNNING" SUCCESS = "SUCCESS" FAILED = "FAILED" SKIPPED = "SKIPPED" class AuditAction(str, Enum): LOGIN = "LOGIN" LOGOUT = "LOGOUT" INSTANCE_CREATE = "INSTANCE_CREATE" INSTANCE_START = "INSTANCE_START" INSTANCE_STOP = "INSTANCE_STOP" INSTANCE_REBOOT = "INSTANCE_REBOOT" INSTANCE_TERMINATE = "INSTANCE_TERMINATE" INSTANCE_SYNC = "INSTANCE_SYNC" CREDENTIAL_CREATE = "CREDENTIAL_CREATE" CREDENTIAL_UPDATE = "CREDENTIAL_UPDATE" CREDENTIAL_DELETE = "CREDENTIAL_DELETE" CUSTOMER_CREATE = "CUSTOMER_CREATE" CUSTOMER_UPDATE = "CUSTOMER_UPDATE" CUSTOMER_DELETE = "CUSTOMER_DELETE" USER_CREATE = "USER_CREATE" USER_UPDATE = "USER_UPDATE" USER_DELETE = "USER_DELETE" OTHER = "OTHER" class AuditResourceType(str, Enum): USER = "USER" CUSTOMER = "CUSTOMER" AWS_CREDENTIAL = "AWS_CREDENTIAL" INSTANCE = "INSTANCE" JOB = "JOB" OTHER = "OTHER" class Role(Base): __tablename__ = "roles" __table_args__ = (UniqueConstraint("name", name="uniq_role_name"),) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(64), nullable=False) description: Mapped[Optional[str]] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) users: Mapped[list["User"]] = relationship("User", back_populates="role") class Customer(Base): __tablename__ = "customers" __table_args__ = (UniqueConstraint("name", name="uniq_customer_name"),) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(128), nullable=False) contact_email: Mapped[Optional[str]] = mapped_column(String(128)) is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False) quota_instances: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True)) notes: Mapped[Optional[str]] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) users: Mapped[list["User"]] = relationship("User", back_populates="customer") instances: Mapped[list["Instance"]] = relationship("Instance", back_populates="customer") customer_credentials: Mapped[list["CustomerCredential"]] = relationship( "CustomerCredential", back_populates="customer" ) jobs: Mapped[list["Job"]] = relationship("Job", back_populates="customer") audit_logs: Mapped[list["AuditLog"]] = relationship("AuditLog", back_populates="customer") class User(Base): __tablename__ = "users" __table_args__ = ( UniqueConstraint("username", name="uniq_username"), UniqueConstraint("email", name="uniq_email"), Index("idx_users_role", "role_id"), Index("idx_users_customer", "customer_id"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False) email: Mapped[Optional[str]] = mapped_column(String(128)) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) role_id: Mapped[int] = mapped_column(ForeignKey("roles.id", ondelete="RESTRICT", onupdate="CASCADE"), nullable=False) customer_id: Mapped[Optional[int]] = mapped_column( ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE") ) is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False) last_login_at: Mapped[Optional[datetime]] = mapped_column(DateTime) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) role: Mapped["Role"] = relationship("Role", back_populates="users") customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="users") jobs: Mapped[list["Job"]] = relationship("Job", back_populates="created_by_user") audit_logs: Mapped[list["AuditLog"]] = relationship("AuditLog", back_populates="user") class AWSCredential(Base): __tablename__ = "aws_credentials" __table_args__ = ( UniqueConstraint("account_id", "name", name="uniq_credential_account_name"), Index("idx_credential_account", "account_id"), Index("idx_credential_active", "is_active"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(128), nullable=False) account_id: Mapped[str] = mapped_column(String(32), nullable=False) credential_type: Mapped[CredentialType] = mapped_column( SAEnum(CredentialType), nullable=False, server_default=text("'ACCESS_KEY'") ) access_key_id: Mapped[Optional[str]] = mapped_column(String(128)) secret_access_key: Mapped[Optional[str]] = mapped_column(String(256)) role_arn: Mapped[Optional[str]] = mapped_column(String(256)) external_id: Mapped[Optional[str]] = mapped_column(String(128)) default_region: Mapped[str] = mapped_column(String(32), nullable=False, server_default=text("'ap-northeast-1'")) is_active: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) instances: Mapped[list["Instance"]] = relationship("Instance", back_populates="credential") customer_credentials: Mapped[list["CustomerCredential"]] = relationship( "CustomerCredential", back_populates="credential" ) class CustomerCredential(Base): __tablename__ = "customer_credentials" __table_args__ = ( UniqueConstraint("customer_id", "credential_id", name="uniq_customer_credential"), Index("idx_cc_customer", "customer_id"), Index("idx_cc_credential", "credential_id"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) customer_id: Mapped[int] = mapped_column( ForeignKey("customers.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False ) credential_id: Mapped[int] = mapped_column( ForeignKey("aws_credentials.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False ) is_allowed: Mapped[int] = mapped_column(TINYINT(1), server_default=text("1"), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) customer: Mapped["Customer"] = relationship("Customer", back_populates="customer_credentials") credential: Mapped["AWSCredential"] = relationship("AWSCredential", back_populates="customer_credentials") class Instance(Base): __tablename__ = "instances" __table_args__ = ( UniqueConstraint("account_id", "region", "instance_id", name="uniq_instance_cloud"), Index("idx_instances_customer", "customer_id"), Index("idx_instances_status", "status"), Index("idx_instances_region", "region"), Index("idx_instances_last_sync", "last_sync"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) customer_id: Mapped[int] = mapped_column( ForeignKey("customers.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False ) credential_id: Mapped[Optional[int]] = mapped_column( ForeignKey("aws_credentials.id", ondelete="SET NULL", onupdate="CASCADE") ) account_id: Mapped[str] = mapped_column(String(32), nullable=False) region: Mapped[str] = mapped_column(String(32), nullable=False) az: Mapped[Optional[str]] = mapped_column(String(32)) instance_id: Mapped[str] = mapped_column(String(32), nullable=False) name_tag: Mapped[Optional[str]] = mapped_column(String(255)) instance_type: Mapped[str] = mapped_column(String(64), nullable=False) ami_id: Mapped[Optional[str]] = mapped_column(String(64)) key_name: Mapped[Optional[str]] = mapped_column(String(128)) public_ip: Mapped[Optional[str]] = mapped_column(String(45)) private_ip: Mapped[Optional[str]] = mapped_column(String(45)) status: Mapped[InstanceStatus] = mapped_column( SAEnum(InstanceStatus), nullable=False, server_default=text("'UNKNOWN'") ) desired_status: Mapped[Optional[InstanceDesiredStatus]] = mapped_column(SAEnum(InstanceDesiredStatus)) security_groups: Mapped[Optional[dict]] = mapped_column(JSON) subnet_id: Mapped[Optional[str]] = mapped_column(String(64)) vpc_id: Mapped[Optional[str]] = mapped_column(String(64)) launched_at: Mapped[Optional[datetime]] = mapped_column(DateTime) terminated_at: Mapped[Optional[datetime]] = mapped_column(DateTime) last_sync: Mapped[Optional[datetime]] = mapped_column(DateTime) last_cloud_state: Mapped[Optional[dict]] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) customer: Mapped["Customer"] = relationship("Customer", back_populates="instances") credential: Mapped[Optional["AWSCredential"]] = relationship("AWSCredential", back_populates="instances") job_items: Mapped[list["JobItem"]] = relationship("JobItem", back_populates="instance") class Job(Base): __tablename__ = "jobs" __table_args__ = ( UniqueConstraint("job_uuid", name="uniq_job_uuid"), Index("idx_jobs_type", "job_type"), Index("idx_jobs_status", "status"), Index("idx_jobs_created_at", "created_at"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) job_uuid: Mapped[str] = mapped_column(String(32), nullable=False) job_type: Mapped[JobType] = mapped_column(SAEnum(JobType), nullable=False) status: Mapped[JobStatus] = mapped_column(SAEnum(JobStatus), nullable=False, server_default=text("'PENDING'")) progress: Mapped[int] = mapped_column(TINYINT(unsigned=True), nullable=False, server_default=text("0")) total_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0")) success_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0")) fail_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0")) skipped_count: Mapped[Optional[int]] = mapped_column(INTEGER(unsigned=True), server_default=text("0")) payload: Mapped[Optional[dict]] = mapped_column(JSON) error_message: Mapped[Optional[str]] = mapped_column(String(512)) created_by_user_id: Mapped[Optional[int]] = mapped_column( ForeignKey("users.id", ondelete="SET NULL", onupdate="CASCADE") ) created_for_customer: Mapped[Optional[int]] = mapped_column( ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE") ) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) started_at: Mapped[Optional[datetime]] = mapped_column(DateTime) finished_at: Mapped[Optional[datetime]] = mapped_column(DateTime) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) created_by_user: Mapped[Optional["User"]] = relationship("User", back_populates="jobs") customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="jobs") items: Mapped[list["JobItem"]] = relationship("JobItem", back_populates="job") class JobItem(Base): __tablename__ = "job_items" __table_args__ = ( Index("idx_job_items_job", "job_id"), Index("idx_job_items_instance", "resource_id"), Index("idx_job_items_status", "status"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) job_id: Mapped[int] = mapped_column(ForeignKey("jobs.id", ondelete="CASCADE", onupdate="CASCADE"), nullable=False) resource_type: Mapped[JobItemResourceType] = mapped_column( SAEnum(JobItemResourceType), nullable=False, server_default=text("'INSTANCE'") ) resource_id: Mapped[Optional[int]] = mapped_column( ForeignKey("instances.id", ondelete="SET NULL", onupdate="CASCADE") ) account_id: Mapped[Optional[str]] = mapped_column(String(32)) region: Mapped[Optional[str]] = mapped_column(String(32)) instance_id: Mapped[Optional[str]] = mapped_column(String(32)) action: Mapped[JobItemAction] = mapped_column(SAEnum(JobItemAction), nullable=False) status: Mapped[JobItemStatus] = mapped_column( SAEnum(JobItemStatus), nullable=False, server_default=text("'PENDING'") ) error_message: Mapped[Optional[str]] = mapped_column(String(512)) extra: Mapped[Optional[dict]] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False ) job: Mapped["Job"] = relationship("Job", back_populates="items") instance: Mapped[Optional["Instance"]] = relationship("Instance", back_populates="job_items") class AuditLog(Base): __tablename__ = "audit_logs" __table_args__ = ( Index("idx_audit_customer", "customer_id"), Index("idx_audit_action", "action"), Index("idx_audit_created_at", "created_at"), Index("idx_audit_resource", "resource_type", "resource_id"), ) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) user_id: Mapped[Optional[int]] = mapped_column( ForeignKey("users.id", ondelete="SET NULL", onupdate="CASCADE") ) customer_id: Mapped[Optional[int]] = mapped_column( ForeignKey("customers.id", ondelete="SET NULL", onupdate="CASCADE") ) action: Mapped[AuditAction] = mapped_column(SAEnum(AuditAction), nullable=False) resource_type: Mapped[AuditResourceType] = mapped_column(SAEnum(AuditResourceType), nullable=False) resource_id: Mapped[Optional[int]] = mapped_column(BIGINT(unsigned=True)) description: Mapped[Optional[str]] = mapped_column(String(512)) payload: Mapped[Optional[dict]] = mapped_column(JSON) ip_address: Mapped[Optional[str]] = mapped_column(String(45)) user_agent: Mapped[Optional[str]] = mapped_column(String(255)) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"), nullable=False) user: Mapped[Optional["User"]] = relationship("User", back_populates="audit_logs") customer: Mapped[Optional["Customer"]] = relationship("Customer", back_populates="audit_logs") class Setting(Base): __tablename__ = "settings" __table_args__ = (UniqueConstraint("k", name="uniq_settings_key"),) id: Mapped[int] = mapped_column(BIGINT(unsigned=True), primary_key=True, autoincrement=True) k: Mapped[str] = mapped_column(String(128), nullable=False) v: Mapped[Optional[str]] = mapped_column(Text) description: Mapped[Optional[str]] = mapped_column(String(255)) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP"), nullable=False )