Skip to content
73 changes: 73 additions & 0 deletions alembic/versions/a1e2c51ddaac_multitenancy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Multitenancy

Revision ID: a1e2c51ddaac
Revises: b648caf8d8fb
Create Date: 2025-12-12 18:13:56.743605

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'a1e2c51ddaac'
down_revision = 'b648caf8d8fb'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('event_brand',
sa.Column('uuid', sa.UUID(), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.PrimaryKeyConstraint('uuid', name=op.f('pk_event_brand')),
sa.UniqueConstraint('uuid', name=op.f('uq_event_brand_uuid'))
)
op.add_column('crew', sa.Column('event_brand', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_crew_event_brand_event_brand'), 'crew', 'event_brand', ['event_brand'], ['uuid'])
op.add_column('event', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_event_event_brand_uuid_event_brand'), 'event', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('location', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_location_event_brand_uuid_event_brand'), 'location', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('position', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_position_event_brand_uuid_event_brand'), 'position', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('seatmap', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_seatmap_event_brand_uuid_event_brand'), 'seatmap', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('seatmap_background', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_seatmap_background_event_brand_uuid_event_brand'), 'seatmap_background', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('store_session', sa.Column('event_uuid', sa.UUID(), nullable=False))
op.create_foreign_key(op.f('fk_store_session_event_uuid_event'), 'store_session', 'event', ['event_uuid'], ['uuid'])
op.add_column('ticket_type', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_ticket_type_event_brand_uuid_event_brand'), 'ticket_type', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('ticket_voucher', sa.Column('event_brand_uuid', sa.UUID(), nullable=False))
op.create_foreign_key(op.f('fk_ticket_voucher_event_brand_uuid_event_brand'), 'ticket_voucher', 'event_brand', ['event_brand_uuid'], ['uuid'])
op.add_column('user_positions', sa.Column('event_brand_uuid', sa.UUID(), nullable=True))
op.create_foreign_key(op.f('fk_user_positions_event_brand_uuid_event_brand'), 'user_positions', 'event_brand', ['event_brand_uuid'], ['uuid'])
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('fk_user_positions_event_brand_uuid_event_brand'), 'user_positions', type_='foreignkey')
op.drop_column('user_positions', 'event_brand_uuid')
op.drop_constraint(op.f('fk_ticket_voucher_event_brand_uuid_event_brand'), 'ticket_voucher', type_='foreignkey')
op.drop_column('ticket_voucher', 'event_brand_uuid')
op.drop_constraint(op.f('fk_ticket_type_event_brand_uuid_event_brand'), 'ticket_type', type_='foreignkey')
op.drop_column('ticket_type', 'event_brand_uuid')
op.drop_constraint(op.f('fk_store_session_event_uuid_event'), 'store_session', type_='foreignkey')
op.drop_column('store_session', 'event_uuid')
op.drop_constraint(op.f('fk_seatmap_background_event_brand_uuid_event_brand'), 'seatmap_background', type_='foreignkey')
op.drop_column('seatmap_background', 'event_brand_uuid')
op.drop_constraint(op.f('fk_seatmap_event_brand_uuid_event_brand'), 'seatmap', type_='foreignkey')
op.drop_column('seatmap', 'event_brand_uuid')
op.drop_constraint(op.f('fk_position_event_brand_uuid_event_brand'), 'position', type_='foreignkey')
op.drop_column('position', 'event_brand_uuid')
op.drop_constraint(op.f('fk_location_event_brand_uuid_event_brand'), 'location', type_='foreignkey')
op.drop_column('location', 'event_brand_uuid')
op.drop_constraint(op.f('fk_event_event_brand_uuid_event_brand'), 'event', type_='foreignkey')
op.drop_column('event', 'event_brand_uuid')
op.drop_constraint(op.f('fk_crew_event_brand_event_brand'), 'crew', type_='foreignkey')
op.drop_column('crew', 'event_brand')
op.drop_table('event_brand')
# ### end Alembic commands ###
32 changes: 27 additions & 5 deletions phoenixRest/models/core/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
Integer,
Boolean,
Enum,
Table
Table,
func
)
from sqlalchemy.dialects.postgresql import UUID

Expand All @@ -17,6 +18,7 @@

from phoenixRest.models import Base

from phoenixRest.models.core.event_brand import EventBrand
from phoenixRest.models.core.user import User
from phoenixRest.models.tickets.ticket import Ticket
from phoenixRest.models.tickets.ticket_type import TicketType
Expand All @@ -41,6 +43,9 @@ class Event(Base):
__tablename__ = "event"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand", back_populates="events")

booking_time = Column(DateTime, nullable=False)

# Delta in seconds from booking time
Expand Down Expand Up @@ -69,20 +74,22 @@ class Event(Base):

cancellation_reason = Column(Text)

def __init__(self, name: str, start_time: DateTime, end_time: DateTime, max_participants: int):
def __init__(self, name: str, start_time: DateTime, end_time: DateTime, max_participants: int, event_brand):
self.name = name
self.start_time = start_time
self.end_time = end_time
self.booking_time = start_time - timedelta(days=31)
self.priority_seating_time_delta = 60*30
self.seating_time_delta = 60*60
self.max_participants = max_participants
self.event_brand = event_brand


def __json__(self, request):
return {
'name': str(self.name),
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'participant_age_limit_inclusive': self.participant_age_limit_inclusive,
'crew_age_limit_inclusive': self.crew_age_limit_inclusive,
'booking_time': int(self.booking_time.timestamp()),
Expand All @@ -109,10 +116,25 @@ def get_total_ticket_availability(self, request):
)) \
.count()

def get_current_event(request):
firstEvent = request.db.query(Event).filter(Event.end_time > datetime.now()).order_by(Event.start_time.asc()).first()
def get_current_events(db):
"""Returns the uuid of all active events, one per brand.
We use uuid as it is more useful"""
ranked_sub = db.query(
Event.uuid.label("uuid"),
func.row_number().over(partition_by=Event.event_brand, order_by=Event.end_time.asc()).label("rank")
).filter(Event.end_time > datetime.now()).subquery()

current_events = list(map(lambda row: row[0], db.query(ranked_sub.c.uuid).filter(ranked_sub.c.rank == 1).all()))
log.info(f"Current events: {current_events}")

return current_events


def get_current_event(db, brand: "Brand"):
"""Returns the current event for a given brand"""
firstEvent = db.query(Event).filter(Event.end_time > datetime.now(), Event.event_brand_uuid == brand.uuid).order_by(Event.start_time.asc()).first()
if firstEvent is None:
logging.warning("There are no new events")
logging.warning(f"There are no new events for brand {brand.uuid}")
return None
else:
# TODO we want to return ticket types some time? Maybe?
Expand Down
41 changes: 41 additions & 0 deletions phoenixRest/models/core/event_brand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from sqlalchemy import (
Column,
DateTime,
ForeignKey,
Integer,
UniqueConstraint,
Text,
Integer,
Boolean,
Enum,
Table
)
from sqlalchemy.dialects.postgresql import UUID

from sqlalchemy.orm import relationship
from sqlalchemy import and_

from phoenixRest.models import Base

import logging
log = logging.getLogger(__name__)

import uuid

class EventBrand(Base):
__tablename__ = "event_brand"

uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

events = relationship("Event", back_populates="event_brand")

name = Column(Text, nullable=False)

def __init__(self, name: str):
self.name = name

def __json__(self, request):
return {
'uuid': str(self.uuid),
'name': str(self.name)
}
6 changes: 6 additions & 0 deletions phoenixRest/models/core/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
)
from sqlalchemy.dialects.postgresql import UUID

from sqlalchemy.orm import relationship

from phoenixRest.models import Base

import uuid
Expand All @@ -21,6 +23,9 @@ class Location(Base):
__tablename__ = "location"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

name = Column(Text, nullable=False)
address = Column(Text, nullable=False)

Expand All @@ -31,6 +36,7 @@ def __init__(self, name: str, address: str):
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'name': self.name,
'address': self.address,
}
7 changes: 6 additions & 1 deletion phoenixRest/models/crew/crew.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from phoenixRest.models import Base

from phoenixRest.models.core.event_brand import EventBrand
from phoenixRest.models.core.user import User

from datetime import datetime, timedelta
Expand All @@ -27,7 +28,10 @@
class Crew(Base):
__tablename__ = "crew"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)


event_brand = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event = relationship("EventBrand")

name = Column(Text, nullable=False)
description = Column(Text, nullable=False)

Expand All @@ -49,6 +53,7 @@ def __init__(self, name: str, description: str):
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'name': self.name,
'description': self.description,
'active': self.active,
Expand Down
4 changes: 4 additions & 0 deletions phoenixRest/models/crew/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class Position(Base):
__tablename__ = "position"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

crew_uuid = Column(UUID(as_uuid=True), ForeignKey("crew.uuid"), nullable=True)
crew = relationship("Crew")

Expand All @@ -49,6 +52,7 @@ def __init__(self, name: str, description: str):
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'name': self.name,
'description': self.description,
'is_vanity': self.is_vanity,
Expand Down
4 changes: 4 additions & 0 deletions phoenixRest/models/crew/position_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class PositionMapping(Base):
__tablename__ = "user_positions"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

position_uuid = Column(UUID(as_uuid=True), ForeignKey("position.uuid"), nullable=False)
position = relationship("Position", back_populates="position_mappings", uselist=False)

Expand All @@ -46,6 +49,7 @@ def __init__(self, user, position, event=None):
def __json__(self, request):
return {
'uuid': self.uuid,
'event_brand_uuid': str(self.event_brand_uuid),
'position_uuid': self.position_uuid,
'user_uuid': self.user_uuid,
'event_uuid': self.event_uuid,
Expand Down
4 changes: 4 additions & 0 deletions phoenixRest/models/tickets/seatmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class Seatmap(Base):
__tablename__ = "seatmap"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

name = Column(Text, nullable=False)
description = Column(Text, nullable=False)

Expand All @@ -50,6 +53,7 @@ def __init__(self, name: str, description: str):
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'name': str(self.name),
'description': str(self.description),
'background': map_seatmap_background_no_metadata(self.background, request) if self.background is not None else None,
Expand Down
4 changes: 4 additions & 0 deletions phoenixRest/models/tickets/seatmap_background.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class SeatmapBackground(Base):
__tablename__ = "seatmap_background"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

created = Column(DateTime, nullable=False, server_default='NOW()')
extension = Column(Text, nullable=False)

Expand All @@ -47,6 +50,7 @@ def get_fs_location(self, request):
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'uploader_uuid': self.uploader_uuid,
'created': int(self.created.timestamp()),
'url': self.get_url(request)
Expand Down
7 changes: 6 additions & 1 deletion phoenixRest/models/tickets/store_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,19 @@ class StoreSession(Base):
user_uuid = Column(UUID(as_uuid=True), ForeignKey("user.uuid"), nullable=False)
user = relationship("User")

event_uuid = Column(UUID(as_uuid=True), ForeignKey("event.uuid"), nullable=False)
event = relationship("Event")

payment = relationship("Payment", back_populates="store_session")

cart_entries = relationship("StoreSessionCartEntry", back_populates="store_session")

created = Column(DateTime, nullable=False)
expires = Column(DateTime, nullable=False)

def __init__(self, user: User, expiry: int):
def __init__(self, user: User, expiry: int, event):
self.user = user
self.event = event
self.created = datetime.now()
self.expires = self.created + timedelta(seconds=expiry)

Expand All @@ -51,6 +55,7 @@ def __json__(self, request):
return {
'uuid': str(self.uuid),
'user_uuid': self.user_uuid,
'event_uuid': self.event_uuid,
'entries': self.cart_entries,
'total': self.get_total(),
'created': int(self.created.timestamp()),
Expand Down
6 changes: 6 additions & 0 deletions phoenixRest/models/tickets/ticket_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
)
from sqlalchemy.dialects.postgresql import UUID

from sqlalchemy.orm import relationship

from phoenixRest.models import Base

import uuid
Expand All @@ -20,6 +22,9 @@ class TicketType(Base):
__tablename__ = "ticket_type"
uuid = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, unique=True, nullable=False)

event_brand_uuid = Column(UUID(as_uuid=True), ForeignKey("event_brand.uuid"), nullable=True)
event_brand = relationship("EventBrand")

name = Column(Text, nullable=False)

price = Column(Integer, nullable=False)
Expand All @@ -45,6 +50,7 @@ def __init__(self, name: str, price: int, description: str, refundable: bool, se
def __json__(self, request):
return {
'uuid': str(self.uuid),
'event_brand_uuid': str(self.event_brand_uuid),
'name': self.name,
'price': self.price,
'refundable': self.refundable,
Expand Down
Loading