Files
gatehouse-api/gatehouse_app/models/zerotier/device.py
T
JamesBhattarai 2b6f7e15af Feat(Fix): Multi-Tenant Zerotier Org Setups
Imports Network From Zerotier
Async Emails
Migration guardrails
Admin to see all approvals states
2026-03-31 12:33:56 +05:45

80 lines
2.6 KiB
Python

"""Device model — a user-registered ZeroTier node endpoint."""
from gatehouse_app.extensions import db
from gatehouse_app.models.base import BaseModel
from gatehouse_app.utils.constants import DeviceStatus
class Device(BaseModel):
"""A user-owned endpoint identified by a ZeroTier Node ID.
A user registers their device in the portal using the 10-character ZeroTier
Node ID visible in the ZeroTier client. One active device record per
node_id at a time (unique constraint excludes soft-deleted duplicates).
Attributes:
user_id: FK to the owning user
organization_id: FK to the organization this device is registered in
node_id: 10-char hex ZeroTier node / device address
device_nickname: User-assigned friendly name
hostname: Device hostname reported by the client
asset_tag: Corporate asset tag if available
serial_number: Device serial number if available
status: active / inactive
"""
__tablename__ = "devices"
user_id = db.Column(
db.String(36),
db.ForeignKey("users.id"),
nullable=False,
index=True,
)
organization_id = db.Column(
db.String(36),
db.ForeignKey("organizations.id"),
nullable=False,
index=True,
)
node_id = db.Column(
db.String(10),
nullable=False,
index=True,
)
device_nickname = db.Column(db.String(255), nullable=True)
hostname = db.Column(db.String(255), nullable=True)
asset_tag = db.Column(db.String(255), nullable=True)
serial_number = db.Column(db.String(255), nullable=True)
status = db.Column(
db.Enum(DeviceStatus, name="device_status", values_callable=lambda x: [e.value for e in x]),
default=DeviceStatus.ACTIVE,
nullable=False,
)
# Relationships
user = db.relationship("User", backref="devices")
organization = db.relationship("Organization", backref="devices")
memberships = db.relationship(
"DeviceNetworkMembership",
back_populates="device",
cascade="all, delete-orphan",
)
def __repr__(self):
return f"<Device {self.node_id} ({self.device_nickname or 'unnamed'})>"
@property
def display_name(self) -> str:
return self.device_nickname or self.hostname or self.node_id
def to_dict(self, exclude=None):
exclude = exclude or []
data = super().to_dict(exclude=exclude)
data["display_name"] = self.display_name
data["active_membership_count"] = sum(
1 for m in self.memberships
if m.state == "active_authorized" and m.deleted_at is None
)
return data