Compare commits
2 commits
e706b46003
...
26c0a5c429
| Author | SHA1 | Date | |
|---|---|---|---|
| 26c0a5c429 | |||
| 209a895415 |
2 changed files with 79 additions and 7 deletions
22
models.py
22
models.py
|
|
@ -58,7 +58,8 @@ class TicketWave(BaseModel):
|
||||||
|
|
||||||
|
|
||||||
class EventExtraBase(BaseModel):
|
class EventExtraBase(BaseModel):
|
||||||
"""Everything in `extra` that is safe to show anyone. `EventExtra` adds
|
"""Everything in `extra` that is safe to show anyone — ticket waves
|
||||||
|
included, since a buyer needs a wave id to choose one. `EventExtra` adds
|
||||||
the organizer-only promo codes on top; `PublicEventExtra` is this base,
|
the organizer-only promo codes on top; `PublicEventExtra` is this base,
|
||||||
so anonymous responses can never carry them."""
|
so anonymous responses can never carry them."""
|
||||||
|
|
||||||
|
|
@ -73,6 +74,19 @@ class EventExtraBase(BaseModel):
|
||||||
# `effective_payment_methods`. Same field name/shape as upstream v2 so the
|
# `effective_payment_methods`. Same field name/shape as upstream v2 so the
|
||||||
# eventual rebase (#33) merges cleanly.
|
# eventual rebase (#33) merges cleanly.
|
||||||
payment_methods: list[str] = Field(default_factory=list)
|
payment_methods: list[str] = Field(default_factory=list)
|
||||||
|
# Upstream v1.6.8 ticket waves — time-boxed pricing tiers. The
|
||||||
|
# event-level `currency` / `allow_fiat` / `amount_tickets` /
|
||||||
|
# `price_per_ticket` fields become derived values (see
|
||||||
|
# `sync_event_ticket_waves`), which is why fork code that reads them
|
||||||
|
# needs auditing — aiolabs/events#61.
|
||||||
|
#
|
||||||
|
# Public, not organizer-only: a buyer cannot choose a wave without its
|
||||||
|
# id, and neither the public event response nor the NIP-52 tags carried
|
||||||
|
# one before. A wave holds price, dates and remaining stock — the sales
|
||||||
|
# information a buyer needs — so the only thing exposing it reveals is
|
||||||
|
# the upcoming price schedule, which is what #61 regretted giving up
|
||||||
|
# when it settled on flat Nostr tags.
|
||||||
|
ticket_waves: list[TicketWave] = Field(default_factory=list)
|
||||||
|
|
||||||
@validator("payment_methods", pre=True)
|
@validator("payment_methods", pre=True)
|
||||||
def normalize_payment_methods(cls, v):
|
def normalize_payment_methods(cls, v):
|
||||||
|
|
@ -92,12 +106,6 @@ class EventExtraBase(BaseModel):
|
||||||
|
|
||||||
class EventExtra(EventExtraBase):
|
class EventExtra(EventExtraBase):
|
||||||
promo_codes: list[PromoCode] = Field(default_factory=list)
|
promo_codes: list[PromoCode] = Field(default_factory=list)
|
||||||
# Upstream v1.6.8 ticket waves — time-boxed pricing tiers. The
|
|
||||||
# event-level `currency` / `allow_fiat` / `amount_tickets` /
|
|
||||||
# `price_per_ticket` fields become derived values (see
|
|
||||||
# `sync_event_ticket_waves`), which is why fork code that reads them
|
|
||||||
# needs auditing — aiolabs/events#61.
|
|
||||||
ticket_waves: list[TicketWave] = Field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
PublicEventExtra = EventExtraBase
|
PublicEventExtra = EventExtraBase
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ from ..models import (
|
||||||
CreateEvent,
|
CreateEvent,
|
||||||
CreateTicket,
|
CreateTicket,
|
||||||
EventExtra,
|
EventExtra,
|
||||||
|
PromoCode,
|
||||||
|
PublicEvent,
|
||||||
|
PublicEventExtra,
|
||||||
|
TicketWave,
|
||||||
effective_payment_methods,
|
effective_payment_methods,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -108,3 +112,63 @@ def test_payment_methods_are_normalised_and_deduplicated():
|
||||||
def test_unknown_payment_method_is_rejected():
|
def test_unknown_payment_method_is_rejected():
|
||||||
with pytest.raises(ValidationError):
|
with pytest.raises(ValidationError):
|
||||||
EventExtra(payment_methods=["cash"])
|
EventExtra(payment_methods=["cash"])
|
||||||
|
|
||||||
|
|
||||||
|
# --- public projection ------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_extra_exposes_waves_but_never_promo_codes():
|
||||||
|
"""A buyer needs a wave id to choose a tier, so waves are public; promo
|
||||||
|
codes stay organizer-only (aiolabs/events#61, v1.6.1-aio.12)."""
|
||||||
|
organizer = EventExtra(
|
||||||
|
promo_codes=[PromoCode(code="SECRET", discount_percent=50)],
|
||||||
|
ticket_waves=[
|
||||||
|
TicketWave(
|
||||||
|
id="early",
|
||||||
|
title="Early",
|
||||||
|
opening_date="2030-01-01",
|
||||||
|
closing_date="2030-02-01",
|
||||||
|
currency="sat",
|
||||||
|
price_per_ticket=10,
|
||||||
|
amount_tickets=5,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
public = PublicEventExtra(**organizer.dict())
|
||||||
|
|
||||||
|
assert [wave.id for wave in public.ticket_waves] == ["early"]
|
||||||
|
assert public.ticket_waves[0].price_per_ticket == 10
|
||||||
|
assert not hasattr(public, "promo_codes")
|
||||||
|
assert "promo_codes" not in public.dict()
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_event_response_carries_waves():
|
||||||
|
"""End of the chain: what `GET /events/{id}` actually serialises."""
|
||||||
|
wave = TicketWave(
|
||||||
|
id="regular",
|
||||||
|
title="Regular",
|
||||||
|
opening_date="2030-01-01",
|
||||||
|
closing_date="2030-02-01",
|
||||||
|
currency="sat",
|
||||||
|
price_per_ticket=25,
|
||||||
|
amount_tickets=40,
|
||||||
|
)
|
||||||
|
organizer_extra = EventExtra(
|
||||||
|
ticket_waves=[wave], promo_codes=[PromoCode(code="SECRET")]
|
||||||
|
)
|
||||||
|
public = PublicEvent(
|
||||||
|
id="evt",
|
||||||
|
name="Test",
|
||||||
|
info="",
|
||||||
|
canceled=False,
|
||||||
|
event_start_date="2030-01-01",
|
||||||
|
currency="sat",
|
||||||
|
price_per_ticket=25,
|
||||||
|
banner=None,
|
||||||
|
extra=PublicEventExtra(**organizer_extra.dict()),
|
||||||
|
)
|
||||||
|
body = public.dict()
|
||||||
|
|
||||||
|
assert [w["id"] for w in body["extra"]["ticket_waves"]] == ["regular"]
|
||||||
|
assert "promo_codes" not in body["extra"]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue