AWS-Panel/backend/models.py
2025-12-10 12:02:17 +08:00

407 lines
18 KiB
Python

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
)