Spaces:
Runtime error
Runtime error
| from sqlalchemy import String, Integer, Boolean, ForeignKey, DateTime, Text, JSON, Numeric, Enum, Float | |
| from sqlalchemy.orm import Mapped, mapped_column, relationship | |
| from datetime import datetime | |
| from decimal import Decimal | |
| import enum | |
| from .db import Base | |
| class TransactionType(str, enum.Enum): | |
| """Credit transaction types""" | |
| RESET = "reset" | |
| DEDUCT = "deduct" | |
| REFUND = "refund" | |
| ADJUSTMENT = "adjustment" | |
| class QuestionType(str, enum.Enum): | |
| """Form question types""" | |
| SHORT_ANSWER = "short_answer" | |
| LONG_ANSWER = "long_answer" | |
| MULTIPLE_CHOICE = "multiple_choice" | |
| CHECKBOXES = "checkboxes" | |
| DROPDOWN = "dropdown" | |
| MULTI_SELECT = "multi_select" | |
| NUMBER = "number" | |
| EMAIL = "email" | |
| PHONE = "phone" | |
| LINK = "link" | |
| FILE_UPLOAD = "file_upload" | |
| DATE = "date" | |
| TIME = "time" | |
| LINEAR_SCALE = "linear_scale" | |
| MATRIX = "matrix" | |
| RATING = "rating" | |
| PAYMENT = "payment" | |
| SIGNATURE = "signature" | |
| RANKING = "ranking" | |
| WALLET_CONNECT = "wallet_connect" | |
| class ConditionType(str, enum.Enum): | |
| """Conditional logic condition types""" | |
| EQUALS = "equals" | |
| NOT_EQUALS = "not_equals" | |
| CONTAINS = "contains" | |
| NOT_CONTAINS = "not_contains" | |
| GREATER_THAN = "greater_than" | |
| LESS_THAN = "less_than" | |
| IS_EMPTY = "is_empty" | |
| IS_NOT_EMPTY = "is_not_empty" | |
| class SubmissionStatus(str, enum.Enum): | |
| """Form submission status types""" | |
| IN_PROGRESS = "in_progress" | |
| PARTIAL = "partial" | |
| COMPLETE = "complete" | |
| ABANDONED = "abandoned" | |
| class AnalyticsEventType(str, enum.Enum): | |
| """Analytics event types""" | |
| FORM_VIEWED = "form_viewed" | |
| FORM_STARTED = "form_started" | |
| QUESTION_VIEWED = "question_viewed" | |
| QUESTION_ANSWERED = "question_answered" | |
| QUESTION_SKIPPED = "question_skipped" | |
| FORM_ABANDONED = "form_abandoned" | |
| FORM_SUBMITTED_PARTIAL = "form_submitted_partial" | |
| FORM_SUBMITTED_COMPLETE = "form_submitted_complete" | |
| class User(Base): | |
| __tablename__ = "users" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| email: Mapped[str] = mapped_column(String(255), unique=True, index=True) | |
| name: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| picture: Mapped[str | None] = mapped_column(String(512), nullable=True) | |
| provider: Mapped[str | None] = mapped_column(String(50), nullable=True) | |
| provider_id: Mapped[str | None] = mapped_column(String(255), index=True) | |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| subscriptions: Mapped[list["Subscription"]] = relationship(back_populates="user") | |
| credits: Mapped["UserCredits"] = relationship(back_populates="user", uselist=False) | |
| credit_transactions: Mapped[list["CreditTransaction"]] = relationship(back_populates="user") | |
| forms: Mapped[list["Form"]] = relationship(back_populates="user", cascade="all, delete-orphan") | |
| public_forms: Mapped[list["PublicForm"]] = relationship(back_populates="user", cascade="all, delete-orphan") | |
| form_uploads: Mapped[list["FormUpload"]] = relationship(back_populates="user", cascade="all, delete-orphan") | |
| class Subscription(Base): | |
| __tablename__ = "subscriptions" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) | |
| plan_id: Mapped[int | None] = mapped_column(ForeignKey("subscription_plans.id"), nullable=True) | |
| status: Mapped[str] = mapped_column(String(50), default="inactive") | |
| stripe_customer_id: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| stripe_subscription_id: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| current_period_start: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| current_period_end: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| cancel_at_period_end: Mapped[bool] = mapped_column(Boolean, default=False) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| user: Mapped[User] = relationship(back_populates="subscriptions") | |
| plan: Mapped["SubscriptionPlan"] = relationship(back_populates="subscriptions") | |
| class SubscriptionPlan(Base): | |
| __tablename__ = "subscription_plans" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| name: Mapped[str] = mapped_column(String(100), unique=True, index=True) | |
| stripe_price_id: Mapped[str | None] = mapped_column(String(255), nullable=True) # Legacy field, use stripe_price_id_monthly | |
| stripe_price_id_monthly: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| stripe_price_id_yearly: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| stripe_product_id: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| price_monthly: Mapped[Decimal] = mapped_column(Numeric(10, 2), default=0.0) | |
| price_yearly: Mapped[Decimal | None] = mapped_column(Numeric(10, 2), nullable=True) | |
| # Credit configuration | |
| credits_per_month: Mapped[int] = mapped_column(Integer, default=0) | |
| credits_per_analyze: Mapped[int] = mapped_column(Integer, default=5) | |
| credits_per_edit: Mapped[int] = mapped_column(Integer, default=2) | |
| # Extensible features as JSON | |
| features: Mapped[dict | None] = mapped_column(JSON, nullable=True) | |
| # Plan management | |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True) | |
| sort_order: Mapped[int] = mapped_column(Integer, default=0) | |
| # Timestamps | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| subscriptions: Mapped[list["Subscription"]] = relationship(back_populates="plan") | |
| user_credits: Mapped[list["UserCredits"]] = relationship(back_populates="plan") | |
| class UserCredits(Base): | |
| __tablename__ = "user_credits" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True, index=True) | |
| plan_id: Mapped[int | None] = mapped_column(ForeignKey("subscription_plans.id"), nullable=True) | |
| balance: Mapped[int] = mapped_column(Integer, default=0) | |
| last_reset_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| user: Mapped[User] = relationship(back_populates="credits") | |
| plan: Mapped["SubscriptionPlan"] = relationship(back_populates="user_credits") | |
| class CreditTransaction(Base): | |
| __tablename__ = "credit_transactions" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) | |
| amount: Mapped[int] = mapped_column(Integer) # Can be negative for deductions | |
| transaction_type: Mapped[TransactionType] = mapped_column(Enum(TransactionType), index=True) | |
| description: Mapped[str] = mapped_column(Text) | |
| transaction_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) | |
| # Relationships | |
| user: Mapped[User] = relationship(back_populates="credit_transactions") | |
| class Form(Base): | |
| """Form table for AI-generated forms""" | |
| __tablename__ = "forms" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) | |
| title: Mapped[str] = mapped_column(String(255)) | |
| description: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| settings: Mapped[dict | None] = mapped_column(JSON, nullable=True) # Theme colors, submit button text, etc. | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| user: Mapped[User] = relationship(back_populates="forms") | |
| questions: Mapped[list["FormQuestion"]] = relationship(back_populates="form", cascade="all, delete-orphan", order_by="FormQuestion.question_order") | |
| conditional_rules: Mapped[list["ConditionalRule"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| responses: Mapped[list["FormResponse"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| chat_messages: Mapped[list["ChatMessage"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| versions: Mapped[list["FormVersion"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| analytics_events: Mapped[list["FormAnalyticsEvent"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| uploads: Mapped[list["FormUpload"]] = relationship(back_populates="form", cascade="all, delete-orphan") | |
| class FormQuestion(Base): | |
| """Form questions table""" | |
| __tablename__ = "form_questions" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| question_order: Mapped[int] = mapped_column(Integer) | |
| question_type: Mapped[QuestionType] = mapped_column(Enum(QuestionType)) | |
| question_text: Mapped[str] = mapped_column(Text) | |
| description: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| required: Mapped[bool] = mapped_column(Boolean, default=False) | |
| settings: Mapped[dict | None] = mapped_column(JSON, nullable=True) # Type-specific options (choices, validation, etc.) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="questions") | |
| trigger_rules: Mapped[list["ConditionalRule"]] = relationship(foreign_keys="ConditionalRule.trigger_question_id", back_populates="trigger_question", cascade="all, delete-orphan") | |
| target_rules: Mapped[list["ConditionalRule"]] = relationship(foreign_keys="ConditionalRule.target_question_id", back_populates="target_question", cascade="all, delete-orphan") | |
| answers: Mapped[list["ResponseAnswer"]] = relationship(back_populates="question", cascade="all, delete-orphan") | |
| analytics_events: Mapped[list["FormAnalyticsEvent"]] = relationship(back_populates="question", cascade="all, delete-orphan") | |
| uploads: Mapped[list["FormUpload"]] = relationship(back_populates="question", cascade="all, delete-orphan") | |
| class ConditionalRule(Base): | |
| """Conditional logic rules for showing/hiding questions""" | |
| __tablename__ = "conditional_rules" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| trigger_question_id: Mapped[int] = mapped_column(ForeignKey("form_questions.id"), index=True) | |
| target_question_id: Mapped[int] = mapped_column(ForeignKey("form_questions.id"), index=True) | |
| condition_type: Mapped[ConditionType] = mapped_column(Enum(ConditionType)) | |
| condition_value: Mapped[str | None] = mapped_column(Text, nullable=True) # The value to compare against | |
| action: Mapped[str] = mapped_column(String(20), default="show") # "show" or "hide" | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="conditional_rules") | |
| trigger_question: Mapped[FormQuestion] = relationship(foreign_keys=[trigger_question_id], back_populates="trigger_rules") | |
| target_question: Mapped[FormQuestion] = relationship(foreign_keys=[target_question_id], back_populates="target_rules") | |
| class FormResponse(Base): | |
| """Form submission responses""" | |
| __tablename__ = "form_responses" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| # Enhanced tracking fields | |
| status: Mapped[str] = mapped_column(String(20), default="complete", index=True) | |
| form_version: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| session_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) | |
| # Timestamps | |
| submitted_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) | |
| started_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| last_updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, onupdate=datetime.utcnow) | |
| # IP and geolocation | |
| ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) # IPv6 compatible (legacy) | |
| ip_address_hash: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| country: Mapped[str | None] = mapped_column(String(100), nullable=True) | |
| city: Mapped[str | None] = mapped_column(String(100), nullable=True) | |
| # Traffic source tracking | |
| user_agent: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| referrer: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| utm_source: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| utm_medium: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| utm_campaign: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| submission_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) # Additional submission metadata | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="responses") | |
| answers: Mapped[list["ResponseAnswer"]] = relationship(back_populates="response", cascade="all, delete-orphan") | |
| analytics_events: Mapped[list["FormAnalyticsEvent"]] = relationship(back_populates="submission", cascade="all, delete-orphan") | |
| uploads: Mapped[list["FormUpload"]] = relationship(back_populates="response", cascade="all, delete-orphan") | |
| class ResponseAnswer(Base): | |
| """Individual answers within a form response""" | |
| __tablename__ = "response_answers" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_response_id: Mapped[int] = mapped_column(ForeignKey("form_responses.id"), index=True) | |
| form_question_id: Mapped[int] = mapped_column(ForeignKey("form_questions.id"), index=True) | |
| answer_value: Mapped[dict | None] = mapped_column(JSON, nullable=True) # JSON to handle all answer types | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| # Relationships | |
| response: Mapped[FormResponse] = relationship(back_populates="answers") | |
| question: Mapped[FormQuestion] = relationship(back_populates="answers") | |
| class FormUpload(Base): | |
| """Uploaded files for form responses""" | |
| __tablename__ = "form_uploads" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| form_response_id: Mapped[int | None] = mapped_column(ForeignKey("form_responses.id"), nullable=True, index=True) | |
| form_question_id: Mapped[int] = mapped_column(ForeignKey("form_questions.id"), index=True) | |
| status: Mapped[str] = mapped_column(String(20), default="pending", index=True) | |
| original_filename: Mapped[str] = mapped_column(String(512)) | |
| content_type: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| s3_key: Mapped[str] = mapped_column(String(1024), unique=True, index=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| uploaded_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| attached_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| user: Mapped[User] = relationship(back_populates="form_uploads") | |
| form: Mapped[Form] = relationship(back_populates="uploads") | |
| response: Mapped["FormResponse"] = relationship(back_populates="uploads") | |
| question: Mapped[FormQuestion] = relationship(back_populates="uploads") | |
| class PublicForm(Base): | |
| """Public shareable forms""" | |
| __tablename__ = "public_forms" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) | |
| share_token: Mapped[str] = mapped_column(String(64), unique=True, index=True) | |
| is_public: Mapped[bool] = mapped_column(Boolean, default=True) | |
| expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) | |
| # Settings | |
| allow_multiple_submissions: Mapped[bool] = mapped_column(Boolean, default=True) | |
| collect_email: Mapped[bool] = mapped_column(Boolean, default=False) | |
| custom_thank_you_message: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| # OG Image for social sharing (generated on publish) | |
| og_image_url: Mapped[str | None] = mapped_column(String(512), nullable=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| form: Mapped[Form] = relationship() | |
| user: Mapped[User] = relationship(back_populates="public_forms") | |
| class ChatMessage(Base): | |
| """Unified chat messages for all AI assistant conversations (form editing + response analysis)""" | |
| __tablename__ = "chat_messages" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) | |
| # Chat type: "form_editing" for add/edit, "response_analysis" for queries | |
| chat_type: Mapped[str] = mapped_column(String(30), default="form_editing", index=True) | |
| role: Mapped[str] = mapped_column(String(20)) # "user", "assistant" | |
| content: Mapped[str] = mapped_column(Text) | |
| query_type: Mapped[str | None] = mapped_column(String(50), nullable=True) # e.g., "add_component", "edit_component", "summary", "filter" | |
| # Form editing specific | |
| question_index: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| # Response analysis specific | |
| sql_query: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| result_data: Mapped[dict | None] = mapped_column(JSON, nullable=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="chat_messages") | |
| user: Mapped[User] = relationship() | |
| class FormVersion(Base): | |
| """Form version snapshots for validation and history""" | |
| __tablename__ = "form_versions" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| version: Mapped[int] = mapped_column(Integer, nullable=False) | |
| schema_snapshot: Mapped[dict] = mapped_column(JSON, nullable=False) # Full form + questions structure | |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="versions") | |
| class FormAnalyticsEvent(Base): | |
| """Analytics event tracking for forms""" | |
| __tablename__ = "form_analytics_events" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| event_id: Mapped[str] = mapped_column(String(36), unique=True, nullable=False, index=True) # UUID | |
| # Foreign keys | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), index=True) | |
| submission_id: Mapped[int | None] = mapped_column(ForeignKey("form_responses.id"), nullable=True, index=True) | |
| question_id: Mapped[int | None] = mapped_column(ForeignKey("form_questions.id"), nullable=True, index=True) | |
| # Event details | |
| event_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True) | |
| session_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) | |
| # IP and geolocation | |
| ip_address_hash: Mapped[str] = mapped_column(String(64), nullable=False) | |
| ip_address_raw: Mapped[str | None] = mapped_column(String(45), nullable=True) # Only if user opts in | |
| country: Mapped[str | None] = mapped_column(String(100), nullable=True) | |
| city: Mapped[str | None] = mapped_column(String(100), nullable=True) | |
| # Traffic source | |
| user_agent: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| referrer: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| utm_source: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| utm_medium: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| utm_campaign: Mapped[str | None] = mapped_column(String(255), nullable=True) | |
| # Event-specific data | |
| time_spent_seconds: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| event_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True) | |
| # Relationships | |
| form: Mapped[Form] = relationship(back_populates="analytics_events") | |
| submission: Mapped["FormResponse"] = relationship(back_populates="analytics_events") | |
| question: Mapped["FormQuestion"] = relationship(back_populates="analytics_events") | |
| class SubmissionRateLimit(Base): | |
| """Rate limiting for form submissions""" | |
| __tablename__ = "submission_rate_limits" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| identifier: Mapped[str] = mapped_column(String(64), nullable=False, index=True) # IP hash or device fingerprint | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), nullable=False, index=True) | |
| submission_count: Mapped[int] = mapped_column(Integer, default=0) | |
| window_start: Mapped[datetime] = mapped_column(DateTime, nullable=False) | |
| is_blocked: Mapped[bool] = mapped_column(Boolean, default=False) | |
| # Relationships | |
| form: Mapped[Form] = relationship() | |
| class WebhookConfig(Base): | |
| """Webhook configuration for forms""" | |
| __tablename__ = "webhook_configs" | |
| id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) | |
| form_id: Mapped[int] = mapped_column(ForeignKey("forms.id"), unique=True, nullable=False, index=True) | |
| user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) | |
| webhook_url: Mapped[str] = mapped_column(Text, nullable=False) | |
| secret: Mapped[str] = mapped_column(String(128), nullable=False) | |
| is_active: Mapped[bool] = mapped_column(Boolean, default=True) | |
| events: Mapped[list] = mapped_column(JSON, nullable=False) # Array of event types to trigger on | |
| created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) | |
| updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) | |
| # Relationships | |
| form: Mapped[Form] = relationship() | |
| user: Mapped[User] = relationship() | |