from pydantic import BaseModel, EmailStr, Field, ConfigDict from typing import Optional from datetime import datetime, date from ..models.models import UserRole, MembershipStatus, PaymentStatus, PaymentMethod # User Schemas class UserBase(BaseModel): email: EmailStr first_name: str = Field(..., min_length=1, max_length=100) last_name: str = Field(..., min_length=1, max_length=100) phone: Optional[str] = None address: Optional[str] = None class UserCreate(UserBase): password: str = Field(..., min_length=8) class UserUpdate(BaseModel): email: Optional[EmailStr] = None first_name: Optional[str] = Field(None, min_length=1, max_length=100) last_name: Optional[str] = Field(None, min_length=1, max_length=100) phone: Optional[str] = None address: Optional[str] = None role: Optional[UserRole] = None class UserResponse(UserBase): model_config = ConfigDict(from_attributes=True) id: int role: UserRole is_active: bool created_at: datetime last_login: Optional[datetime] = None class UserInDB(UserResponse): hashed_password: str # Authentication Schemas class Token(BaseModel): access_token: str token_type: str = "bearer" class TokenData(BaseModel): user_id: Optional[int] = None class LoginRequest(BaseModel): email: EmailStr password: str # Password Reset Schemas class ForgotPasswordRequest(BaseModel): email: EmailStr class ResetPasswordRequest(BaseModel): token: str = Field(..., min_length=1) new_password: str = Field(..., min_length=8) class ChangePasswordRequest(BaseModel): current_password: str = Field(..., min_length=1) new_password: str = Field(..., min_length=8) # Membership Tier Schemas class MembershipTierBase(BaseModel): name: str = Field(..., min_length=1, max_length=100) description: Optional[str] = None annual_fee: float = Field(..., ge=0) benefits: Optional[str] = None class MembershipTierCreate(MembershipTierBase): pass class MembershipTierUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=100) description: Optional[str] = None annual_fee: Optional[float] = Field(None, ge=0) benefits: Optional[str] = None is_active: Optional[bool] = None class MembershipTierResponse(MembershipTierBase): model_config = ConfigDict(from_attributes=True) id: int is_active: bool created_at: datetime # Membership Schemas class MembershipBase(BaseModel): tier_id: int auto_renew: bool = False class MembershipCreate(MembershipBase): start_date: date end_date: date class MembershipUpdate(BaseModel): tier_id: Optional[int] = None status: Optional[MembershipStatus] = None end_date: Optional[date] = None auto_renew: Optional[bool] = None class MembershipResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int user_id: int tier_id: int status: MembershipStatus start_date: date end_date: date auto_renew: bool created_at: datetime tier: MembershipTierResponse # Payment Schemas class PaymentBase(BaseModel): amount: float = Field(..., gt=0) payment_method: PaymentMethod notes: Optional[str] = None class PaymentCreate(PaymentBase): membership_id: Optional[int] = None class PaymentUpdate(BaseModel): status: Optional[PaymentStatus] = None transaction_id: Optional[str] = None payment_date: Optional[datetime] = None notes: Optional[str] = None class PaymentResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int user_id: int membership_id: Optional[int] = None amount: float payment_method: PaymentMethod status: PaymentStatus transaction_id: Optional[str] = None payment_date: Optional[datetime] = None notes: Optional[str] = None created_at: datetime # Square Payment Schemas class SquarePaymentRequest(BaseModel): """Request schema for Square payment processing""" source_id: str = Field(..., description="Payment source ID from Square Web Payments SDK") tier_id: int = Field(..., description="Membership tier ID to create membership for") amount: float = Field(..., gt=0, description="Payment amount in GBP") idempotency_key: Optional[str] = Field(None, description="Unique key to prevent duplicate payments") note: Optional[str] = Field(None, description="Optional payment note") billing_details: Optional[dict] = Field(None, description="Billing address and cardholder name for AVS") class SquarePaymentResponse(BaseModel): """Response schema for Square payment""" success: bool payment_id: Optional[str] = None status: Optional[str] = None amount: Optional[float] = None currency: Optional[str] = None receipt_url: Optional[str] = None errors: Optional[list[str]] = None database_payment_id: Optional[int] = None membership_id: Optional[int] = Field(None, description="Created membership ID") class SquareRefundRequest(BaseModel): """Request schema for Square payment refund""" payment_id: int = Field(..., description="Database payment ID") amount: Optional[float] = Field(None, gt=0, description="Amount to refund (None for full refund)") reason: Optional[str] = Field(None, description="Reason for refund") # Message Response class MessageResponse(BaseModel): message: str detail: Optional[str] = None # Email Template Schemas class EmailTemplateBase(BaseModel): template_key: str name: str subject: str html_body: str text_body: Optional[str] = None variables: Optional[str] = None class EmailTemplateCreate(EmailTemplateBase): pass class EmailTemplateUpdate(BaseModel): name: Optional[str] = None subject: Optional[str] = None html_body: Optional[str] = None text_body: Optional[str] = None variables: Optional[str] = None is_active: Optional[bool] = None class EmailTemplateResponse(EmailTemplateBase): model_config = ConfigDict(from_attributes=True) id: int is_active: bool created_at: datetime updated_at: datetime # Event Schemas class EventBase(BaseModel): title: str = Field(..., min_length=1, max_length=255) description: Optional[str] = None event_date: datetime event_time: Optional[str] = Field(None, pattern=r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$') location: Optional[str] = None max_attendees: Optional[int] = Field(None, gt=0) class EventCreate(EventBase): pass class EventUpdate(BaseModel): title: Optional[str] = Field(None, min_length=1, max_length=255) description: Optional[str] = None event_date: Optional[datetime] = None event_time: Optional[str] = Field(None, pattern=r'^([01]?[0-9]|2[0-3]):[0-5][0-9]$') location: Optional[str] = None max_attendees: Optional[int] = Field(None, gt=0) status: Optional[str] = None class EventResponse(EventBase): model_config = ConfigDict(from_attributes=True) id: int status: str created_by: int created_at: datetime updated_at: datetime # Event RSVP Schemas class EventRSVPBase(BaseModel): status: str = Field(..., pattern="^(pending|attending|not_attending|maybe)$") notes: Optional[str] = None class EventRSVPUpdate(EventRSVPBase): pass class EventRSVPResponse(EventRSVPBase): model_config = ConfigDict(from_attributes=True) id: int event_id: int user_id: int attended: bool created_at: datetime updated_at: datetime