Core modules: - Laws: CRUD, search, AI-powered QA - Analysis: legal research and case management - Contracts: lifecycle management with templates - Signatures: electronic signature workflow Infrastructure: - FastAPI + SQLite + async SQLAlchemy - Docker deployment support - 54 unit tests passing Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
"""Signature model for electronic signatures."""
|
|
import enum
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import String, Text, DateTime, Enum as SQLEnum, Integer, ForeignKey, JSON
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class SignatureStatus(str, enum.Enum):
|
|
"""Signature status enumeration."""
|
|
PENDING = "pending"
|
|
SIGNED = "signed"
|
|
REJECTED = "rejected"
|
|
EXPIRED = "expired"
|
|
|
|
|
|
class SignatureRequest(Base):
|
|
"""Signature request model."""
|
|
|
|
__tablename__ = "signature_requests"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
contract_id: Mapped[int] = mapped_column(Integer, ForeignKey("contracts.id"), index=True)
|
|
requester_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), index=True)
|
|
signer_name: Mapped[str] = mapped_column(String(100))
|
|
signer_email: Mapped[str] = mapped_column(String(100))
|
|
status: Mapped[SignatureStatus] = mapped_column(
|
|
SQLEnum(SignatureStatus),
|
|
default=SignatureStatus.PENDING
|
|
)
|
|
token: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
|
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
|
|
def __init__(
|
|
self,
|
|
contract_id: int,
|
|
requester_id: int,
|
|
signer_name: str,
|
|
signer_email: str,
|
|
token: str,
|
|
expires_at: datetime,
|
|
status: SignatureStatus = SignatureStatus.PENDING,
|
|
**kwargs
|
|
):
|
|
self.contract_id = contract_id
|
|
self.requester_id = requester_id
|
|
self.signer_name = signer_name
|
|
self.signer_email = signer_email
|
|
self.token = token
|
|
self.expires_at = expires_at
|
|
self.status = status
|
|
self.created_at = datetime.utcnow()
|
|
|
|
|
|
class Signature(Base):
|
|
"""Signature record model."""
|
|
|
|
__tablename__ = "signatures"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
request_id: Mapped[int] = mapped_column(Integer, ForeignKey("signature_requests.id"), index=True)
|
|
signer_name: Mapped[str] = mapped_column(String(100))
|
|
signature_data: Mapped[str] = mapped_column(Text) # Base64 image or coordinates
|
|
ip_address: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
|
user_agent: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
|
|
verification_hash: Mapped[str] = mapped_column(String(64)) # Document fingerprint
|
|
signed_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
|
|
def __init__(
|
|
self,
|
|
request_id: int,
|
|
signer_name: str,
|
|
signature_data: str,
|
|
verification_hash: str,
|
|
ip_address: Optional[str] = None,
|
|
user_agent: Optional[str] = None,
|
|
**kwargs
|
|
):
|
|
self.request_id = request_id
|
|
self.signer_name = signer_name
|
|
self.signature_data = signature_data
|
|
self.verification_hash = verification_hash
|
|
self.ip_address = ip_address
|
|
self.user_agent = user_agent
|
|
self.signed_at = datetime.utcnow()
|
|
|
|
|
|
class SignatureAudit(Base):
|
|
"""Signature audit log model."""
|
|
|
|
__tablename__ = "signature_audits"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
signature_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("signatures.id"), nullable=True)
|
|
request_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("signature_requests.id"), nullable=True)
|
|
action: Mapped[str] = mapped_column(String(50))
|
|
details: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
|
|
ip_address: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
|
|
|
def __init__(
|
|
self,
|
|
action: str,
|
|
signature_id: Optional[int] = None,
|
|
request_id: Optional[int] = None,
|
|
details: Optional[dict] = None,
|
|
ip_address: Optional[str] = None,
|
|
**kwargs
|
|
):
|
|
self.action = action
|
|
self.signature_id = signature_id
|
|
self.request_id = request_id
|
|
self.details = details
|
|
self.ip_address = ip_address
|
|
self.created_at = datetime.utcnow()
|