"""ZeroTier membership model — observed controller-side member state.""" from gatehouse_app.extensions import db from gatehouse_app.models.base import BaseModel class ZeroTierMembership(BaseModel): """Observed state of a node in a ZeroTier network from the controller API. This is maintained as a cache of controller-side state, updated by the reconciliation loop and by direct API calls. The raw_controller_payload column stores the full API response for debugging and audit. Keyed by zerotier_network_id + node_id (unique constraint). Attributes: organization_id: FK to the organization device_network_membership_id: FK to the portal's membership record (nullable) zerotier_network_id: The 16-char hex ZeroTier network ID node_id: The 10-char hex ZeroTier node ID member_seen: Whether the controller has ever seen this member authorized: Current authorization state from ZeroTier join_seen_at: When the member was first seen joining last_synced_at: When we last polled ZeroTier for this member raw_controller_payload: Full API response for debugging """ __tablename__ = "zerotier_memberships" organization_id = db.Column( db.String(36), db.ForeignKey("organizations.id"), nullable=False, index=True, ) device_network_membership_id = db.Column( db.String(36), db.ForeignKey("device_network_memberships.id"), nullable=True, index=True, ) zerotier_network_id = db.Column( db.String(16), nullable=False, index=True, ) node_id = db.Column( db.String(10), nullable=False, index=True, ) member_seen = db.Column(db.Boolean, default=False, nullable=False) authorized = db.Column(db.Boolean, default=False, nullable=False) join_seen_at = db.Column(db.DateTime(timezone=True), nullable=True) last_synced_at = db.Column(db.DateTime(timezone=True), nullable=True) raw_controller_payload = db.Column(db.JSON, nullable=True) # Relationships organization = db.relationship("Organization", backref="zerotier_memberships") device_network_membership = db.relationship( "DeviceNetworkMembership", back_populates="zerotier_membership", ) __table_args__ = ( db.UniqueConstraint( "zerotier_network_id", "node_id", name="uix_zt_network_node", ), ) def __repr__(self): return ( f"" ) def to_dict(self, exclude=None): exclude = exclude or [] data = super().to_dict(exclude=exclude) return data