Files
----/backend/app/models/models.py
smolkik-code 9b15f8b09c feat: CMDB full-stack app - FastAPI + PostgreSQL + React
- PostgreSQL schema: 14 tables, JSONB attributes, audit triggers, soft delete
- FastAPI backend: CRUD, search/filter, relationship graph, bulk import, JWT RBAC
- React frontend: CI table, detail card, force-graph, dashboard
- Seed data: homelab scenario (Proxmox, Mikrotik, VMs, services)
- Docker Compose + Kubernetes manifests
- 20 backend tests (pytest + httpx)
2026-06-25 13:01:40 +07:00

223 lines
8.9 KiB
Python

import uuid
from datetime import datetime, date
from sqlalchemy import (
Column, String, Text, Boolean, Integer, Numeric, Date,
DateTime, ForeignKey, Enum as SAEnum, UniqueConstraint, Index
)
from sqlalchemy.dialects.postgresql import UUID, INET, MACADDR, JSONB, ARRAY
from sqlalchemy.orm import relationship
from app.database import Base
class Location(Base):
__tablename__ = "locations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(Text, nullable=False, unique=True)
description = Column(Text)
parent_id = Column(UUID(as_uuid=True), ForeignKey("locations.id"), nullable=True)
metadata_ = Column("metadata", JSONB, default={})
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
username = Column(Text, nullable=False, unique=True)
email = Column(Text, nullable=False, unique=True)
full_name = Column(Text)
role = Column(SAEnum("admin", "editor", "viewer", name="user_role"), nullable=False, default="viewer")
team = Column(Text)
is_active = Column(Boolean, nullable=False, default=True)
password_hash = Column(Text, nullable=False)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class CIClass(Base):
__tablename__ = "ci_classes"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(Text, nullable=False, unique=True)
description = Column(Text)
parent_id = Column(UUID(as_uuid=True), ForeignKey("ci_classes.id"), nullable=True)
icon = Column(Text)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class CIType(Base):
__tablename__ = "ci_types"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
class_id = Column(UUID(as_uuid=True), ForeignKey("ci_classes.id"), nullable=False)
name = Column(Text, nullable=False)
description = Column(Text)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
__table_args__ = (
UniqueConstraint("class_id", "name"),
)
class ConfigurationItem(Base):
__tablename__ = "configuration_items"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_type_id = Column(UUID(as_uuid=True), ForeignKey("ci_types.id"), nullable=False)
name = Column(Text, nullable=False)
description = Column(Text)
status = Column(
SAEnum("active", "inactive", "maintenance", "deprecated", "planned", name="ci_status"),
nullable=False, default="active"
)
location_id = Column(UUID(as_uuid=True), ForeignKey("locations.id"), nullable=True)
serial_number = Column(Text)
asset_tag = Column(Text)
purchase_date = Column(Date)
warranty_expiry = Column(Date)
attributes = Column(JSONB, default={})
tags = Column(ARRAY(Text), default=[])
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"))
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
version = Column(Integer, nullable=False, default=1)
ci_type = relationship("CIType", lazy="joined")
location = relationship("Location", lazy="joined")
created_by_user = relationship("User", lazy="joined")
__table_args__ = (
Index("idx_ci_name_type_active", "name", "ci_type_id", unique=True,
postgresql_where="deleted_at IS NULL"),
)
class IPAddress(Base):
__tablename__ = "ip_addresses"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
ip_address = Column(INET, nullable=False)
subnet_mask = Column(INET)
gateway = Column(INET)
dns_servers = Column(ARRAY(INET))
is_primary = Column(Boolean, nullable=False, default=False)
vlan_id = Column(Integer)
dhcp_enabled = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class NetworkInterface(Base):
__tablename__ = "network_interfaces"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
name = Column(Text, nullable=False)
mac_address = Column(MACADDR)
speed_mbps = Column(Integer)
interface_type = Column(Text, default="ethernet")
is_up = Column(Boolean, default=True)
ip_address_id = Column(UUID(as_uuid=True), ForeignKey("ip_addresses.id"), nullable=True)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class HardwareDetail(Base):
__tablename__ = "hardware_details"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
manufacturer = Column(Text)
model = Column(Text)
cpu_model = Column(Text)
cpu_cores = Column(Integer)
ram_gb = Column(Numeric(10, 2))
storage_gb = Column(Numeric(10, 2))
storage_type = Column(Text)
form_factor = Column(Text)
power_supply = Column(Text)
bios_version = Column(Text)
serial_number = Column(Text)
specs = Column(JSONB, default={})
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class SoftwareInstance(Base):
__tablename__ = "software_instances"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
name = Column(Text, nullable=False)
vendor = Column(Text)
version = Column(Text)
license_key = Column(Text)
license_type = Column(Text)
install_path = Column(Text)
config_path = Column(Text)
port = Column(Integer)
protocol = Column(Text)
start_command = Column(Text)
service_user = Column(Text)
config = Column(JSONB, default={})
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
updated_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
class CIRelationship(Base):
__tablename__ = "ci_relationships"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
source_ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
target_ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
relationship = Column(
SAEnum("depends_on", "connected_to", "hosted_on", "runs_on",
"manages", "contains", "part_of", "related_to", name="relationship_type"),
nullable=False
)
description = Column(Text)
metadata_ = Column("metadata", JSONB, default={})
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
deleted_at = Column(DateTime(timezone=True), nullable=True)
source_ci = relationship("ConfigurationItem", foreign_keys=[source_ci_id], lazy="joined")
target_ci = relationship("ConfigurationItem", foreign_keys=[target_ci_id], lazy="joined")
__table_args__ = (
UniqueConstraint("source_ci_id", "target_ci_id", "relationship"),
)
class ChangeLog(Base):
__tablename__ = "changelog"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
ci_id = Column(UUID(as_uuid=True), ForeignKey("configuration_items.id"), nullable=False)
action = Column(
SAEnum("create", "update", "delete", "restore",
"relationship_add", "relationship_remove", name="change_action"),
nullable=False
)
changed_by = Column(UUID(as_uuid=True), ForeignKey("users.id"))
field_name = Column(Text)
old_value = Column(Text)
new_value = Column(Text)
snapshot = Column(JSONB)
version = Column(Integer, nullable=False)
comment = Column(Text)
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)