v1
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
class EventCreate(BaseModel):
|
||||
event_type: str = Field(..., max_length=64)
|
||||
page: str = Field(..., max_length=255)
|
||||
element: Optional[str] = Field(None, max_length=255)
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
session_id: Optional[str] = Field(None, max_length=64)
|
||||
|
||||
|
||||
class EventResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
event_type: str
|
||||
page: str
|
||||
element: Optional[str]
|
||||
session_id: str
|
||||
ip_partial: Optional[str]
|
||||
browser: Optional[str]
|
||||
os_name: Optional[str]
|
||||
country: Optional[str]
|
||||
city: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class DailyStat(BaseModel):
|
||||
date: str
|
||||
count: int
|
||||
|
||||
|
||||
class TopItem(BaseModel):
|
||||
label: str
|
||||
count: int
|
||||
|
||||
|
||||
class AnalyticsSummary(BaseModel):
|
||||
total_events_today: int
|
||||
total_events_yesterday: int
|
||||
page_views_today: int
|
||||
unique_sessions_today: int
|
||||
unique_sessions_total: int
|
||||
total_events_all_time: int
|
||||
events_by_type: List[TopItem]
|
||||
top_pages: List[TopItem]
|
||||
top_elements: List[TopItem]
|
||||
top_journeys: List[TopItem]
|
||||
top_browsers: List[TopItem]
|
||||
top_os: List[TopItem]
|
||||
top_countries: List[TopItem]
|
||||
events_last_7_days: List[DailyStat]
|
||||
recent_events: List[EventResponse]
|
||||
|
||||
|
||||
class BookingActivityStat(BaseModel):
|
||||
date: str
|
||||
booked: int
|
||||
cancellations: int
|
||||
|
||||
|
||||
class BookingForwardLoadStat(BaseModel):
|
||||
date: str
|
||||
total: int
|
||||
am: int
|
||||
pm: int
|
||||
|
||||
|
||||
class BookingCustomerVolume(BaseModel):
|
||||
label: str
|
||||
count: int
|
||||
|
||||
|
||||
class BookingOperationsSummary(BaseModel):
|
||||
active_bookings_total: int
|
||||
forward_load_total: int
|
||||
booked_last_30_days: int
|
||||
cancellations_last_30_days: int
|
||||
high_volume_customer_count: int
|
||||
forward_load_next_14_days: List[BookingForwardLoadStat]
|
||||
activity_last_30_days: List[BookingActivityStat]
|
||||
top_high_volume_customers: List[BookingCustomerVolume]
|
||||
@@ -0,0 +1,37 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AuditLogResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
timestamp: datetime
|
||||
member_id: Optional[uuid.UUID]
|
||||
member_email: Optional[str]
|
||||
action_type: str
|
||||
area: str
|
||||
description: str
|
||||
status: str
|
||||
booking_id: Optional[uuid.UUID]
|
||||
error_message: Optional[str]
|
||||
error_detail: Optional[str]
|
||||
ip_address: Optional[str]
|
||||
user_agent: Optional[str]
|
||||
extra: Optional[dict]
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AuditLogPage(BaseModel):
|
||||
items: list[AuditLogResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
total_pages: int
|
||||
|
||||
|
||||
class PageVisitSchema(BaseModel):
|
||||
path: str
|
||||
title: Optional[str] = None
|
||||
@@ -0,0 +1,28 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, EmailStr, ConfigDict
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class RefreshRequest(BaseModel):
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
email: str
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -0,0 +1,57 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ContactLeadCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
email: str
|
||||
phone: Optional[str] = Field(default=None, max_length=50)
|
||||
service: Optional[str] = Field(default=None, max_length=255)
|
||||
services: list[str] = Field(default_factory=list)
|
||||
petName: Optional[str] = Field(default=None, max_length=100)
|
||||
petBreed: Optional[str] = Field(default=None, max_length=100)
|
||||
location: Optional[str] = Field(default=None, max_length=100)
|
||||
serviceAreaStatus: Optional[str] = Field(default=None, max_length=32)
|
||||
message: Optional[str] = Field(default=None, max_length=5000)
|
||||
source: str = Field(default="contact_form", max_length=50)
|
||||
|
||||
|
||||
class ContactLeadResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
full_name: str
|
||||
email: str
|
||||
phone: Optional[str]
|
||||
requested_services: Optional[str]
|
||||
pet_name: Optional[str]
|
||||
pet_breed: Optional[str]
|
||||
suburb: Optional[str]
|
||||
service_area_status: Optional[str]
|
||||
message: Optional[str]
|
||||
source: str
|
||||
status: str
|
||||
notes: Optional[str]
|
||||
invited_at: Optional[datetime]
|
||||
invited_member_id: Optional[uuid.UUID]
|
||||
metadata_json: Optional[dict[str, Any]]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ContactLeadUpdate(BaseModel):
|
||||
status: Optional[str] = Field(default=None, max_length=32)
|
||||
notes: Optional[str] = Field(default=None, max_length=5000)
|
||||
|
||||
|
||||
class ContactLeadInviteRequest(BaseModel):
|
||||
send_email: bool = True
|
||||
|
||||
|
||||
class ContactLeadInviteResponse(BaseModel):
|
||||
lead: ContactLeadResponse
|
||||
member_id: uuid.UUID
|
||||
member_status: str
|
||||
@@ -0,0 +1,154 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
KEY_PATTERN = r"^[a-z0-9_]{3,64}$"
|
||||
SESSION_PATTERN = r"^[A-Za-z0-9_-]{8,128}$"
|
||||
|
||||
|
||||
def _validate_metadata(metadata: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
if metadata is None:
|
||||
return None
|
||||
|
||||
if len(metadata) > 20:
|
||||
raise ValueError("metadata must contain at most 20 keys")
|
||||
|
||||
clean: dict[str, Any] = {}
|
||||
|
||||
for key, value in metadata.items():
|
||||
if not isinstance(key, str) or len(key) > 48:
|
||||
raise ValueError("metadata keys must be strings up to 48 characters")
|
||||
if isinstance(value, (str, int, float, bool)) or value is None:
|
||||
clean[key] = value
|
||||
continue
|
||||
raise ValueError("metadata values must be scalar JSON types")
|
||||
|
||||
return clean
|
||||
|
||||
|
||||
class ExperimentVariantDefinition(BaseModel):
|
||||
variant_key: str = Field(..., pattern=KEY_PATTERN)
|
||||
label: str = Field(..., min_length=1, max_length=120)
|
||||
allocation: int = Field(..., ge=0, le=100)
|
||||
is_control: bool
|
||||
|
||||
|
||||
class ExperimentDefinitionResponse(BaseModel):
|
||||
experiment_key: str = Field(..., pattern=KEY_PATTERN)
|
||||
cookie_name: str = Field(..., min_length=3, max_length=96)
|
||||
name: str
|
||||
description: str | None = None
|
||||
enabled: bool
|
||||
eligible_routes: list[str]
|
||||
variants: list[ExperimentVariantDefinition]
|
||||
|
||||
|
||||
class ExperimentDefinitionUpdate(BaseModel):
|
||||
cookie_name: str = Field(..., min_length=3, max_length=96)
|
||||
name: str = Field(..., min_length=1, max_length=120)
|
||||
description: str | None = Field(default=None, max_length=512)
|
||||
enabled: bool
|
||||
eligible_routes: list[str] = Field(default_factory=list, min_length=1)
|
||||
variants: list[ExperimentVariantDefinition] = Field(..., min_length=2)
|
||||
|
||||
@field_validator("cookie_name")
|
||||
@classmethod
|
||||
def validate_cookie_name(cls, value: str) -> str:
|
||||
if not value.startswith("exp_"):
|
||||
raise ValueError("cookie_name must start with 'exp_'")
|
||||
return value
|
||||
|
||||
@field_validator("eligible_routes")
|
||||
@classmethod
|
||||
def validate_routes(cls, value: list[str]) -> list[str]:
|
||||
normalized: list[str] = []
|
||||
for route in value:
|
||||
if not route.startswith("/"):
|
||||
raise ValueError("eligible routes must start with '/'")
|
||||
normalized.append(route.rstrip("/") or "/")
|
||||
return normalized
|
||||
|
||||
@field_validator("variants")
|
||||
@classmethod
|
||||
def validate_variants(cls, value: list[ExperimentVariantDefinition]) -> list[ExperimentVariantDefinition]:
|
||||
if sum(1 for item in value if item.is_control) != 1:
|
||||
raise ValueError("exactly one control variant is required")
|
||||
if sum(item.allocation for item in value) <= 0:
|
||||
raise ValueError("variant allocation total must be greater than zero")
|
||||
return value
|
||||
|
||||
|
||||
class ExperimentEventBase(BaseModel):
|
||||
experiment_key: str = Field(..., pattern=KEY_PATTERN)
|
||||
variant_key: str = Field(..., pattern=KEY_PATTERN)
|
||||
session_id: str = Field(..., pattern=SESSION_PATTERN)
|
||||
user_id: str | None = Field(None, max_length=64)
|
||||
path: str = Field(..., min_length=1, max_length=255)
|
||||
timestamp: datetime
|
||||
metadata: dict[str, Any] | None = None
|
||||
|
||||
@field_validator("path")
|
||||
@classmethod
|
||||
def validate_path(cls, value: str) -> str:
|
||||
if not value.startswith("/"):
|
||||
raise ValueError("path must start with '/'")
|
||||
return value
|
||||
|
||||
@field_validator("metadata")
|
||||
@classmethod
|
||||
def validate_metadata(cls, value: dict[str, Any] | None) -> dict[str, Any] | None:
|
||||
return _validate_metadata(value)
|
||||
|
||||
|
||||
class ExperimentImpressionCreate(ExperimentEventBase):
|
||||
event_name: str = Field(default="impression", pattern=r"^impression$")
|
||||
|
||||
|
||||
class ExperimentEventCreate(ExperimentEventBase):
|
||||
event_name: str = Field(..., pattern=r"^(cta_click|form_start|form_submit)$")
|
||||
|
||||
|
||||
class ExperimentConversionCreate(ExperimentEventBase):
|
||||
event_name: str = Field(default="conversion", pattern=r"^conversion$")
|
||||
conversion_value: Decimal | None = Field(default=None, max_digits=12, decimal_places=2)
|
||||
|
||||
|
||||
class ExperimentEventResponse(BaseModel):
|
||||
id: UUID
|
||||
experiment_key: str
|
||||
variant_key: str
|
||||
session_id: str
|
||||
user_id: str | None = None
|
||||
path: str
|
||||
event_type: str
|
||||
conversion_value: Decimal | None = None
|
||||
metadata: dict[str, Any] | None = None
|
||||
created_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class ExperimentVariantResult(BaseModel):
|
||||
variant_key: str
|
||||
impressions: int
|
||||
cta_clicks: int
|
||||
form_starts: int
|
||||
form_submits: int
|
||||
conversions: int
|
||||
unique_sessions: int
|
||||
conversion_rate: float
|
||||
conversion_value_total: float
|
||||
|
||||
|
||||
class ExperimentResult(BaseModel):
|
||||
experiment_key: str
|
||||
generated_at: datetime
|
||||
variants: list[ExperimentVariantResult]
|
||||
|
||||
|
||||
class ExperimentIngestResponse(BaseModel):
|
||||
ok: bool
|
||||
accepted: bool
|
||||
@@ -0,0 +1,370 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional, Any
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
# ── Magic link ─────────────────────────────────────────────────────────────────
|
||||
|
||||
class MagicLinkVerifySchema(BaseModel):
|
||||
token: str
|
||||
|
||||
|
||||
# ── Claim ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
class ClaimRequestSchema(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
class ClaimCompleteSchema(BaseModel):
|
||||
email: str
|
||||
code: str
|
||||
password: str
|
||||
|
||||
|
||||
class MemberClaimVerifyCodeSchema(BaseModel):
|
||||
code: str
|
||||
password: str
|
||||
|
||||
|
||||
# ── Auth ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
class MemberLoginSchema(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
class MemberLoginVerifySchema(BaseModel):
|
||||
email: str
|
||||
code: str
|
||||
|
||||
|
||||
class MemberTokenResponse(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class MemberRefreshSchema(BaseModel):
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class MemberLogoutSchema(BaseModel):
|
||||
refresh_token: Optional[str] = None
|
||||
|
||||
|
||||
# ── Profile ────────────────────────────────────────────────────────────────────
|
||||
|
||||
class MemberProfileResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
phone: Optional[str]
|
||||
address: Optional[str]
|
||||
emergency_contact: Optional[str]
|
||||
notifications_enabled: bool
|
||||
is_claimed: bool
|
||||
member_status: str
|
||||
activated_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class MemberProfileUpdate(BaseModel):
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
emergency_contact: Optional[str] = None
|
||||
notifications_enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class MemberOnboardingResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
phone: Optional[str]
|
||||
address: Optional[str]
|
||||
emergency_contact: Optional[str]
|
||||
notifications_enabled: bool
|
||||
onboarding_data: Optional[Any]
|
||||
is_claimed: bool
|
||||
member_status: str
|
||||
claimed_at: Optional[datetime]
|
||||
onboarding_completed_at: Optional[datetime]
|
||||
contract_signed_at: Optional[datetime]
|
||||
contract_signer_name: Optional[str]
|
||||
contract_version: Optional[str]
|
||||
activated_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class MemberOnboardingUpdate(BaseModel):
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
emergency_contact: Optional[str] = None
|
||||
onboarding_data: Optional[Any] = None
|
||||
complete_onboarding: bool = False
|
||||
|
||||
|
||||
class ContractSignSchema(BaseModel):
|
||||
signer_name: str
|
||||
agreed: bool
|
||||
contract_version: Optional[str] = None
|
||||
|
||||
|
||||
# ── Walks ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
class WalkResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
service_type: str
|
||||
walked_at: datetime
|
||||
duration_minutes: int
|
||||
notes: Optional[str]
|
||||
recorded_by: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
# ── Bookings ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class BookingCreate(BaseModel):
|
||||
service_type: str
|
||||
requested_day: Optional[str] = None
|
||||
requested_date: Optional[datetime] = None
|
||||
requested_timeslot: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class BookingResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
service_type: str
|
||||
requested_date: Optional[datetime]
|
||||
status: str
|
||||
notes: Optional[str]
|
||||
admin_notes: Optional[str]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class BookingSlotAvailabilityResponse(BaseModel):
|
||||
slot: str
|
||||
label: str
|
||||
booked: int
|
||||
capacity: int
|
||||
remaining: int
|
||||
is_available: bool
|
||||
|
||||
|
||||
class BookingAvailabilityDayResponse(BaseModel):
|
||||
date: str
|
||||
label: str
|
||||
slots: list[BookingSlotAvailabilityResponse]
|
||||
|
||||
|
||||
class BookingAvailabilityResponse(BaseModel):
|
||||
requested_date: str
|
||||
selected: BookingAvailabilityDayResponse
|
||||
alternatives: list[BookingAvailabilityDayResponse]
|
||||
|
||||
|
||||
# ── Messages ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
subject: str
|
||||
body: str
|
||||
sent_by: Optional[str]
|
||||
read_at: Optional[datetime]
|
||||
created_at: datetime
|
||||
direction: str = "inbound"
|
||||
reply_to_id: Optional[uuid.UUID] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class MemberReplySchema(BaseModel):
|
||||
body: str
|
||||
|
||||
|
||||
# ── Admin: Create Member ───────────────────────────────────────────────────────
|
||||
|
||||
class AdminCreateMember(BaseModel):
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
phone: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
emergency_contact: Optional[str] = None
|
||||
onboarding_data: Optional[Any] = None
|
||||
service_pricing_overrides: Optional[Any] = None
|
||||
force_two_factor: Optional[bool] = None
|
||||
|
||||
|
||||
class AdminMemberResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
phone: Optional[str]
|
||||
address: Optional[str]
|
||||
emergency_contact: Optional[str]
|
||||
notifications_enabled: bool
|
||||
onboarding_data: Optional[Any]
|
||||
is_claimed: bool
|
||||
is_active: bool
|
||||
member_status: str
|
||||
claimed_at: Optional[datetime]
|
||||
onboarding_completed_at: Optional[datetime]
|
||||
contract_signed_at: Optional[datetime]
|
||||
contract_signer_name: Optional[str]
|
||||
contract_version: Optional[str]
|
||||
activated_at: Optional[datetime]
|
||||
service_pricing_overrides: Optional[Any]
|
||||
force_two_factor: Optional[bool]
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AdminMemberUpdate(BaseModel):
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
emergency_contact: Optional[str] = None
|
||||
notifications_enabled: Optional[bool] = None
|
||||
onboarding_data: Optional[Any] = None
|
||||
is_active: Optional[bool] = None
|
||||
member_status: Optional[str] = None
|
||||
service_pricing_overrides: Optional[Any] = None
|
||||
force_two_factor: Optional[bool] = None
|
||||
|
||||
|
||||
class AdminMemberToggleAction(BaseModel):
|
||||
enabled: bool
|
||||
|
||||
|
||||
class AdminBookingResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
member_id: uuid.UUID
|
||||
service_type: str
|
||||
requested_date: Optional[datetime]
|
||||
status: str
|
||||
notes: Optional[str]
|
||||
admin_notes: Optional[str]
|
||||
created_at: datetime
|
||||
# Joined fields
|
||||
member_first_name: Optional[str] = None
|
||||
member_last_name: Optional[str] = None
|
||||
member_email: Optional[str] = None
|
||||
member_dog_name: Optional[str] = None
|
||||
member_dog_breed: Optional[str] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AdminBookingCreate(BaseModel):
|
||||
member_id: uuid.UUID
|
||||
service_type: str
|
||||
requested_date: Optional[datetime] = None
|
||||
status: str = "confirmed"
|
||||
notes: Optional[str] = None
|
||||
admin_notes: Optional[str] = None
|
||||
|
||||
|
||||
class AdminBookingUpdate(BaseModel):
|
||||
requested_date: Optional[datetime] = None
|
||||
status: Optional[str] = None # pending | confirmed | cancelled | completed
|
||||
notes: Optional[str] = None
|
||||
admin_notes: Optional[str] = None
|
||||
|
||||
|
||||
# ── Admin: Record Walk ─────────────────────────────────────────────────────────
|
||||
|
||||
class AdminRecordWalk(BaseModel):
|
||||
member_id: uuid.UUID
|
||||
walked_at: datetime
|
||||
service_type: str
|
||||
duration_minutes: int = 60
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
# ── Admin: Send Message ────────────────────────────────────────────────────────
|
||||
|
||||
class AdminSendMessage(BaseModel):
|
||||
member_id: uuid.UUID
|
||||
subject: str
|
||||
body: str
|
||||
|
||||
|
||||
class AdminNotificationSettingsResponse(BaseModel):
|
||||
automatic_member_notifications_enabled: bool
|
||||
nz_public_holiday_notifications_enabled: bool
|
||||
invoice_reminder_notifications_enabled: bool
|
||||
invoice_day_of_week: int
|
||||
|
||||
|
||||
class AdminNotificationSettingsUpdate(BaseModel):
|
||||
automatic_member_notifications_enabled: Optional[bool] = None
|
||||
nz_public_holiday_notifications_enabled: Optional[bool] = None
|
||||
invoice_reminder_notifications_enabled: Optional[bool] = None
|
||||
invoice_day_of_week: Optional[int] = None
|
||||
|
||||
|
||||
class AdminNotificationRunResponse(BaseModel):
|
||||
automatic_member_notifications_enabled: bool
|
||||
public_holiday_messages_sent: int
|
||||
invoice_reminders_sent: int
|
||||
|
||||
|
||||
class AdminNotificationFeedItemResponse(BaseModel):
|
||||
id: str
|
||||
type: str
|
||||
title: str
|
||||
description: str
|
||||
created_at: datetime
|
||||
href: str
|
||||
|
||||
|
||||
class AdminNotificationsResponse(BaseModel):
|
||||
items: list[AdminNotificationFeedItemResponse]
|
||||
total: int
|
||||
settings: AdminNotificationSettingsResponse
|
||||
|
||||
|
||||
class AdminMessageHistoryResponse(BaseModel):
|
||||
id: uuid.UUID
|
||||
member_id: uuid.UUID
|
||||
member_name: str
|
||||
member_email: str
|
||||
subject: str
|
||||
body: str
|
||||
sent_by: Optional[str]
|
||||
created_at: datetime
|
||||
read_at: Optional[datetime]
|
||||
|
||||
|
||||
# ── Contract ───────────────────────────────────────────────────────────────────
|
||||
|
||||
class ContractResponse(BaseModel):
|
||||
onboarding_data: Optional[Any]
|
||||
member_name: str
|
||||
email: str
|
||||
member_status: str
|
||||
contract_signed_at: Optional[datetime]
|
||||
contract_signer_name: Optional[str]
|
||||
contract_version: Optional[str]
|
||||
activated_at: Optional[datetime]
|
||||
joined_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,36 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class PageBase(BaseModel):
|
||||
title: str
|
||||
slug: str
|
||||
body: str = ""
|
||||
meta_title: Optional[str] = None
|
||||
meta_description: Optional[str] = None
|
||||
og_image_url: Optional[str] = None
|
||||
published: bool = False
|
||||
|
||||
|
||||
class PageCreate(PageBase):
|
||||
pass
|
||||
|
||||
|
||||
class PageUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
slug: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
meta_title: Optional[str] = None
|
||||
meta_description: Optional[str] = None
|
||||
og_image_url: Optional[str] = None
|
||||
published: Optional[bool] = None
|
||||
|
||||
|
||||
class PageResponse(PageBase):
|
||||
id: uuid.UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -0,0 +1,46 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class PostBase(BaseModel):
|
||||
title: str
|
||||
slug: str
|
||||
excerpt: Optional[str] = None
|
||||
body: str = ""
|
||||
author: Optional[str] = None
|
||||
featured_image_url: Optional[str] = None
|
||||
tags: List[str] = []
|
||||
published: bool = False
|
||||
|
||||
|
||||
class PostCreate(PostBase):
|
||||
pass
|
||||
|
||||
|
||||
class PostUpdate(BaseModel):
|
||||
title: Optional[str] = None
|
||||
slug: Optional[str] = None
|
||||
excerpt: Optional[str] = None
|
||||
body: Optional[str] = None
|
||||
author: Optional[str] = None
|
||||
featured_image_url: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
published: Optional[bool] = None
|
||||
|
||||
|
||||
class PostResponse(PostBase):
|
||||
id: uuid.UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class PaginatedPostsResponse(BaseModel):
|
||||
items: List[PostResponse]
|
||||
total: int
|
||||
page: int
|
||||
per_page: int
|
||||
total_pages: int
|
||||
@@ -0,0 +1,91 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, Any
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.services.pricing import default_service_pricing
|
||||
|
||||
|
||||
class SiteSettingsBase(BaseModel):
|
||||
site_name: str = ""
|
||||
tagline: Optional[str] = None
|
||||
logo_url: Optional[str] = None
|
||||
footer_text: Optional[str] = None
|
||||
social_links: Dict[str, Any] = {}
|
||||
automatic_member_notifications_enabled: bool = True
|
||||
nz_public_holiday_notifications_enabled: bool = True
|
||||
invoice_reminder_notifications_enabled: bool = True
|
||||
invoice_day_of_week: int = 1
|
||||
bookings_enabled: bool = True
|
||||
walks_enabled: bool = True
|
||||
messages_enabled: bool = True
|
||||
two_factor_enabled: bool = True
|
||||
audit_history_enabled: bool = True
|
||||
experiments_enabled: bool = True
|
||||
|
||||
|
||||
class SiteSettingsUpdate(BaseModel):
|
||||
site_name: Optional[str] = None
|
||||
tagline: Optional[str] = None
|
||||
logo_url: Optional[str] = None
|
||||
footer_text: Optional[str] = None
|
||||
social_links: Optional[Dict[str, Any]] = None
|
||||
automatic_member_notifications_enabled: Optional[bool] = None
|
||||
nz_public_holiday_notifications_enabled: Optional[bool] = None
|
||||
invoice_reminder_notifications_enabled: Optional[bool] = None
|
||||
invoice_day_of_week: Optional[int] = None
|
||||
bookings_enabled: Optional[bool] = None
|
||||
walks_enabled: Optional[bool] = None
|
||||
messages_enabled: Optional[bool] = None
|
||||
two_factor_enabled: Optional[bool] = None
|
||||
audit_history_enabled: Optional[bool] = None
|
||||
experiments_enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class FeatureSettingsBase(BaseModel):
|
||||
bookings_enabled: bool = True
|
||||
walks_enabled: bool = True
|
||||
messages_enabled: bool = True
|
||||
two_factor_enabled: bool = True
|
||||
audit_history_enabled: bool = True
|
||||
experiments_enabled: bool = True
|
||||
|
||||
|
||||
class FeatureSettingsUpdate(BaseModel):
|
||||
bookings_enabled: Optional[bool] = None
|
||||
walks_enabled: Optional[bool] = None
|
||||
messages_enabled: Optional[bool] = None
|
||||
two_factor_enabled: Optional[bool] = None
|
||||
audit_history_enabled: Optional[bool] = None
|
||||
experiments_enabled: Optional[bool] = None
|
||||
|
||||
|
||||
class FeatureSettingsResponse(FeatureSettingsBase):
|
||||
pass
|
||||
|
||||
|
||||
class ServicePricingSettingsResponse(BaseModel):
|
||||
service_pricing: Dict[str, Any] = Field(default_factory=default_service_pricing)
|
||||
|
||||
|
||||
class ServicePricingSettingsUpdate(BaseModel):
|
||||
service_pricing: Dict[str, Any]
|
||||
|
||||
|
||||
class PlannerWeatherDay(BaseModel):
|
||||
code: int
|
||||
max: int
|
||||
min: int
|
||||
|
||||
|
||||
class PlannerWeatherResponse(BaseModel):
|
||||
fetched_at: datetime
|
||||
weather: Dict[str, PlannerWeatherDay]
|
||||
|
||||
|
||||
class SiteSettingsResponse(SiteSettingsBase):
|
||||
id: uuid.UUID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
Reference in New Issue
Block a user