diff --git a/app.py b/app.py index 372e50c..8fd5263 100644 --- a/app.py +++ b/app.py @@ -40,9 +40,9 @@ async def events_context() -> dict: def create_app() -> "Quart": - from blog.models.ghost_content import Post + from shared.models.ghost_content import Post from models.calendars import Calendar - from market.models.market_place import MarketPlace + from shared.models.market_place import MarketPlace app = create_base_app("events", context_fn=events_context) diff --git a/bp/calendar_entry/services/post_associations.py b/bp/calendar_entry/services/post_associations.py index 023e758..0d60b2d 100644 --- a/bp/calendar_entry/services/post_associations.py +++ b/bp/calendar_entry/services/post_associations.py @@ -5,7 +5,7 @@ from sqlalchemy import select from sqlalchemy.sql import func from models.calendars import CalendarEntry, CalendarEntryPost -from blog.models.ghost_content import Post +from shared.models.ghost_content import Post async def add_post_to_entry( diff --git a/bp/calendars/services/calendars.py b/bp/calendars/services/calendars.py index 59982c1..576be72 100644 --- a/bp/calendars/services/calendars.py +++ b/bp/calendars/services/calendars.py @@ -4,7 +4,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from models.calendars import Calendar -from blog.models.ghost_content import Post # for FK existence checks +from shared.models.ghost_content import Post # for FK existence checks from glue.services.relationships import attach_child, detach_child import unicodedata import re diff --git a/bp/markets/routes.py b/bp/markets/routes.py index ee5f628..3ce15ad 100644 --- a/bp/markets/routes.py +++ b/bp/markets/routes.py @@ -5,7 +5,7 @@ from quart import ( ) from sqlalchemy import select -from market.models.market_place import MarketPlace +from shared.models.market_place import MarketPlace from .services.markets import ( create_market as svc_create_market, diff --git a/bp/markets/services/markets.py b/bp/markets/services/markets.py index 785ec04..24efa78 100644 --- a/bp/markets/services/markets.py +++ b/bp/markets/services/markets.py @@ -6,8 +6,8 @@ import unicodedata from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession -from market.models.market_place import MarketPlace -from blog.models.ghost_content import Post +from shared.models.market_place import MarketPlace +from shared.models.ghost_content import Post from shared.browser.app.utils import utcnow from glue.services.relationships import attach_child, detach_child diff --git a/bp/payments/routes.py b/bp/payments/routes.py index a5c6a84..677bbc8 100644 --- a/bp/payments/routes.py +++ b/bp/payments/routes.py @@ -5,7 +5,7 @@ from quart import ( ) from sqlalchemy import select -from cart.models.page_config import PageConfig +from shared.models.page_config import PageConfig from shared.browser.app.authz import require_admin from shared.browser.app.utils.htmx import is_htmx_request diff --git a/glue b/glue index fc14d83..ebce44e 160000 --- a/glue +++ b/glue @@ -1 +1 @@ -Subproject commit fc14d8323adcb404894c8ab875431c9d571cfabb +Subproject commit ebce44e9d9ae0938647290a1e98687ae79fd71eb diff --git a/models/calendars.py b/models/calendars.py index 67675d4..02025ff 100644 --- a/models/calendars.py +++ b/models/calendars.py @@ -1,296 +1,4 @@ -from __future__ import annotations - -from sqlalchemy import ( - Column, Integer, String, DateTime, ForeignKey, CheckConstraint, - Index, text, Text, Boolean, Time, Numeric +from shared.models.calendars import ( # noqa: F401 + Calendar, CalendarEntry, CalendarSlot, + TicketType, Ticket, CalendarEntryPost, ) -from sqlalchemy.orm import relationship -from sqlalchemy.sql import func - -# Adjust this import to match where your Base lives -from shared.db.base import Base - -from datetime import datetime, timezone - - -def utcnow() -> datetime: - return datetime.now(timezone.utc) - - - -class Calendar(Base): - __tablename__ = "calendars" - - id = Column(Integer, primary_key=True) - container_type = Column(String(32), nullable=False, server_default=text("'page'")) - container_id = Column(Integer, nullable=False) - name = Column(String(255), nullable=False) - description = Column(Text, nullable=True) - slug = Column(String(255), nullable=False) - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - deleted_at = Column(DateTime(timezone=True), nullable=True) - - # relationships - entries = relationship( - "CalendarEntry", - back_populates="calendar", - cascade="all, delete-orphan", - passive_deletes=True, - order_by="CalendarEntry.start_at", - ) - - slots = relationship( - "CalendarSlot", - back_populates="calendar", - cascade="all, delete-orphan", - passive_deletes=True, - order_by="CalendarSlot.time_start", - ) - - # Indexes / constraints - __table_args__ = ( - Index("ix_calendars_container", "container_type", "container_id"), - Index("ix_calendars_name", "name"), - Index("ix_calendars_slug", "slug"), - # Soft-delete-aware uniqueness: one active calendar per container/slug - Index( - "ux_calendars_container_slug_active", - "container_type", - "container_id", - func.lower(slug), - unique=True, - postgresql_where=text("deleted_at IS NULL"), - ), - ) - - -class CalendarEntry(Base): - __tablename__ = "calendar_entries" - - id = Column(Integer, primary_key=True) - calendar_id = Column( - Integer, - ForeignKey("calendars.id", ondelete="CASCADE"), - nullable=False, - index=True, - ) - - # NEW: ownership + order link - user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) - session_id = Column(String(64), nullable=True, index=True) - order_id = Column(Integer, nullable=True, index=True) - - # NEW: slot link - slot_id = Column(Integer, ForeignKey("calendar_slots.id", ondelete="SET NULL"), nullable=True, index=True) - - # details - name = Column(String(255), nullable=False) - start_at = Column(DateTime(timezone=True), nullable=False, index=True) - end_at = Column(DateTime(timezone=True), nullable=True) - - # NEW: booking state + cost - state = Column( - String(20), - nullable=False, - server_default=text("'pending'"), - ) - cost = Column(Numeric(10, 2), nullable=False, server_default=text("10")) - - # Ticket configuration - ticket_price = Column(Numeric(10, 2), nullable=True) # Price per ticket (NULL = no tickets) - ticket_count = Column(Integer, nullable=True) # Total available tickets (NULL = unlimited) - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - deleted_at = Column(DateTime(timezone=True), nullable=True) - - __table_args__ = ( - CheckConstraint( - "(end_at IS NULL) OR (end_at >= start_at)", - name="ck_calendar_entries_end_after_start", - ), - Index("ix_calendar_entries_name", "name"), - Index("ix_calendar_entries_start_at", "start_at"), - Index("ix_calendar_entries_user_id", "user_id"), - Index("ix_calendar_entries_session_id", "session_id"), - Index("ix_calendar_entries_state", "state"), - Index("ix_calendar_entries_order_id", "order_id"), - Index("ix_calendar_entries_slot_id", "slot_id"), - ) - - calendar = relationship("Calendar", back_populates="entries") - slot = relationship("CalendarSlot", back_populates="entries", lazy="selectin") - posts = relationship("CalendarEntryPost", back_populates="entry", cascade="all, delete-orphan") - ticket_types = relationship( - "TicketType", - back_populates="entry", - cascade="all, delete-orphan", - passive_deletes=True, - order_by="TicketType.name", - ) - -DAY_LABELS = [ - ("mon", "Mon"), - ("tue", "Tue"), - ("wed", "Wed"), - ("thu", "Thu"), - ("fri", "Fri"), - ("sat", "Sat"), - ("sun", "Sun"), -] - - -class CalendarSlot(Base): - __tablename__ = "calendar_slots" - - id = Column(Integer, primary_key=True) - calendar_id = Column( - Integer, - ForeignKey("calendars.id", ondelete="CASCADE"), - nullable=False, - index=True, - ) - - name = Column(String(255), nullable=False) - description = Column(Text, nullable=True) - - mon = Column(Boolean, nullable=False, default=False) - tue = Column(Boolean, nullable=False, default=False) - wed = Column(Boolean, nullable=False, default=False) - thu = Column(Boolean, nullable=False, default=False) - fri = Column(Boolean, nullable=False, default=False) - sat = Column(Boolean, nullable=False, default=False) - sun = Column(Boolean, nullable=False, default=False) - - # NEW: whether bookings can be made at flexible times within this band - flexible = Column( - Boolean, - nullable=False, - server_default=text("false"), - default=False, - ) - - @property - def days_display(self) -> str: - days = [label for attr, label in DAY_LABELS if getattr(self, attr)] - if len(days) == len(DAY_LABELS): - # all days selected - return "All" # or "All days" if you prefer - return ", ".join(days) if days else "—" - - time_start = Column(Time(timezone=False), nullable=False) - time_end = Column(Time(timezone=False), nullable=False) - - cost = Column(Numeric(10, 2), nullable=True) - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - deleted_at = Column(DateTime(timezone=True), nullable=True) - - __table_args__ = ( - CheckConstraint( - "(time_end > time_start)", - name="ck_calendar_slots_time_end_after_start", - ), - Index("ix_calendar_slots_calendar_id", "calendar_id"), - Index("ix_calendar_slots_time_start", "time_start"), - ) - - calendar = relationship("Calendar", back_populates="slots") - entries = relationship("CalendarEntry", back_populates="slot") - - -class TicketType(Base): - __tablename__ = "ticket_types" - - id = Column(Integer, primary_key=True) - entry_id = Column( - Integer, - ForeignKey("calendar_entries.id", ondelete="CASCADE"), - nullable=False, - index=True, - ) - - name = Column(String(255), nullable=False) - cost = Column(Numeric(10, 2), nullable=False) - count = Column(Integer, nullable=False) - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - updated_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - deleted_at = Column(DateTime(timezone=True), nullable=True) - - __table_args__ = ( - Index("ix_ticket_types_entry_id", "entry_id"), - Index("ix_ticket_types_name", "name"), - ) - - entry = relationship("CalendarEntry", back_populates="ticket_types") - - -class Ticket(Base): - __tablename__ = "tickets" - - id = Column(Integer, primary_key=True) - entry_id = Column( - Integer, - ForeignKey("calendar_entries.id", ondelete="CASCADE"), - nullable=False, - index=True, - ) - ticket_type_id = Column( - Integer, - ForeignKey("ticket_types.id", ondelete="SET NULL"), - nullable=True, - index=True, - ) - user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) - session_id = Column(String(64), nullable=True, index=True) - order_id = Column(Integer, nullable=True, index=True) - - code = Column(String(64), unique=True, nullable=False) # QR/barcode value - state = Column( - String(20), - nullable=False, - server_default=text("'reserved'"), - ) # reserved, confirmed, checked_in, cancelled - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - checked_in_at = Column(DateTime(timezone=True), nullable=True) - - __table_args__ = ( - Index("ix_tickets_entry_id", "entry_id"), - Index("ix_tickets_ticket_type_id", "ticket_type_id"), - Index("ix_tickets_user_id", "user_id"), - Index("ix_tickets_session_id", "session_id"), - Index("ix_tickets_order_id", "order_id"), - Index("ix_tickets_code", "code", unique=True), - Index("ix_tickets_state", "state"), - ) - - entry = relationship("CalendarEntry", backref="tickets") - ticket_type = relationship("TicketType", backref="tickets") - - -class CalendarEntryPost(Base): - """Junction between calendar entries and content (posts, etc.).""" - __tablename__ = "calendar_entry_posts" - - id = Column(Integer, primary_key=True, autoincrement=True) - entry_id = Column(Integer, ForeignKey("calendar_entries.id", ondelete="CASCADE"), nullable=False) - content_type = Column(String(32), nullable=False, server_default=text("'post'")) - content_id = Column(Integer, nullable=False) - - created_at = Column(DateTime(timezone=True), nullable=False, default=utcnow) - deleted_at = Column(DateTime(timezone=True), nullable=True) - - __table_args__ = ( - Index("ix_entry_posts_entry_id", "entry_id"), - Index("ix_entry_posts_content", "content_type", "content_id"), - ) - - entry = relationship("CalendarEntry", back_populates="posts") - - -__all__ = ["Calendar", "CalendarEntry", "CalendarSlot", "TicketType", "Ticket", "CalendarEntryPost"] diff --git a/shared b/shared index da10fc4..0c0f3c8 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit da10fc4cf91d355342decb2c47e4d4e279e981a1 +Subproject commit 0c0f3c84167ff61ad469e1f5ad93de7841c59e76