"""VPN (Feilian) Full-Mode application ORM model. Mirrors :class:`DomainRegistration` — submission lands in pending, Feishu approval flips to approved, admin can later revoke. Unlike a domain or SSO grant, VPN access is purely personal and does not need a leaver handover: when the requester offboards, IT Admin revokes the account in Feilian directly. """ from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import DateTime, ForeignKey, String, Text, func from sqlalchemy.orm import Mapped, mapped_column from app.core.database import Base VPN_APP_STATUS_PENDING = "pending" VPN_APP_STATUS_APPROVED = "approved" VPN_APP_STATUS_REJECTED = "rejected" VPN_APP_STATUS_CANCELLED = "cancelled" VPN_APP_STATUS_REVOKED = "revoked" # Feilian routes the user picks. Mirrors the Feishu single-choice # widget verbatim — Helix surfaces the same options so we never send a # value the upstream form would reject. Add a new line on the Feishu # side first, then extend this tuple. VPN_NODE_CN4_HK = "CN4-HK" VPN_NODE_CN5_US = "CN5-US" VPN_NODES = (VPN_NODE_CN4_HK, VPN_NODE_CN5_US) class VpnApplication(Base): __tablename__ = "vpn_applications" id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()), ) person_id: Mapped[str] = mapped_column( String(36), ForeignKey("identity_persons.id"), nullable=False, ) vpn_node: Mapped[str] = mapped_column(String(32), nullable=False) usage_description: Mapped[str] = mapped_column(Text, nullable=False) resource_checklist: Mapped[str] = mapped_column(Text, nullable=False) status: Mapped[str] = mapped_column( String(32), nullable=False, default=VPN_APP_STATUS_PENDING, ) feishu_instance_code: Mapped[str | None] = mapped_column( String(64), nullable=True, ) approval_request_id: Mapped[str | None] = mapped_column( String(36), ForeignKey("approval_requests.id", ondelete="SET NULL"), nullable=True, ) granted_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) revoked_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) revoke_reason: Mapped[str | None] = mapped_column(String(255), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False, )