diff --git a/models.py b/models.py
index 1768b44..92dcd84 100644
--- a/models.py
+++ b/models.py
@@ -115,12 +115,7 @@ class CreateEvent(BaseModel):
currency: str = "sat"
allow_fiat: bool = False
fiat_currency: str = "GBP"
- # Capacity is always required and there is no unlimited (#34): a zero
- # here means sold out / not sellable, which `api_get_event` and
- # `api_ticket_create` both enforce with a 410. Under v1.6.8 waves this
- # is only the seed for the primary wave — `sync_event_ticket_waves`
- # recomputes it as the sum of every wave's remaining stock.
- amount_tickets: int = 0
+ amount_tickets: int = 0 # 0 = unlimited / not ticketed
price_per_ticket: float = 0 # 0 = free
banner: str | None = None
location: str | None = None # venue/address (NIP-52 'location' tag)
diff --git a/static/js/index.js b/static/js/index.js
index edddaba..f702a4e 100644
--- a/static/js/index.js
+++ b/static/js/index.js
@@ -901,11 +901,7 @@ window.PageEvents = {
primaryWave.fiat_currency ||
event.fiat_currency ||
'GBP',
- // `??` not `||`: a sold-out wave legitimately holds 0 and must
- // show it rather than silently regaining stock on save. A NEW
- // wave opens at 1, the smallest capacity the backend accepts —
- // there is no unlimited (#34).
- amount_tickets: isEditing ? (wave?.amount_tickets ?? 0) : 1,
+ amount_tickets: wave?.amount_tickets || 0,
price_per_ticket:
wave?.price_per_ticket ||
primaryWave.price_per_ticket ||
diff --git a/static/js/index.vue b/static/js/index.vue
index 3ffe9bb..a52d056 100644
--- a/static/js/index.vue
+++ b/static/js/index.vue
@@ -1094,9 +1094,7 @@
dense
v-model.number="ticketWaveDialog.data.amount_tickets"
type="number"
- min="1"
label="Amount of tickets"
- hint="Tickets on sale in this wave"
>
diff --git a/tests/test_wave_capacity_required.py b/tests/test_wave_capacity_required.py
deleted file mode 100644
index 5f40c4c..0000000
--- a/tests/test_wave_capacity_required.py
+++ /dev/null
@@ -1,118 +0,0 @@
-"""Capacity is required per ticket wave (#34, #62).
-
-"Capacity is always required, there is no unlimited" was settled when
-capacity was one number on the event. Since v1.6.8 it is per-wave and
-`event.amount_tickets` is a derived roll-up, so the rule has to bite where
-the organiser sets it. A zero-capacity wave can never be active
-(`get_active_ticket_waves` requires `> 0`), so it is the wave-level form of
-the trap #62 removed: an event that looks on sale but refuses every
-purchase.
-"""
-
-from datetime import datetime, timedelta, timezone
-from http import HTTPStatus
-
-import pytest
-from fastapi import HTTPException
-
-from ..models import CreateEvent, Event, EventExtra, TicketWave
-from ..views_api import _validate_wave_capacity
-
-TODAY = datetime.now(timezone.utc).date()
-
-
-def _day(offset: int) -> str:
- return (TODAY + timedelta(days=offset)).isoformat()
-
-
-def _wave(wave_id: str, stock: int) -> TicketWave:
- return TicketWave(
- id=wave_id,
- title=wave_id,
- opening_date=_day(0),
- closing_date=_day(20),
- currency="sat",
- price_per_ticket=10,
- amount_tickets=stock,
- )
-
-
-def _create(waves=None, amount_tickets=10) -> CreateEvent:
- return CreateEvent(
- wallet="w",
- name="Fete",
- info="",
- event_start_date=_day(30),
- currency="sat",
- price_per_ticket=10,
- amount_tickets=amount_tickets,
- extra=EventExtra(ticket_waves=waves or []),
- )
-
-
-def _stored(waves) -> Event:
- return Event(
- id="evt",
- wallet="w",
- name="Fete",
- info="",
- closing_date=_day(30),
- event_start_date=_day(30),
- currency="sat",
- price_per_ticket=10,
- amount_tickets=sum(w.amount_tickets for w in waves),
- time=datetime.now(timezone.utc),
- extra=EventExtra(ticket_waves=waves),
- )
-
-
-def _detail(exc_info) -> str:
- assert exc_info.value.status_code == HTTPStatus.BAD_REQUEST
- return exc_info.value.detail
-
-
-def test_wave_with_capacity_is_accepted():
- _validate_wave_capacity(_create([_wave("early", 5), _wave("late", 10)]))
-
-
-def test_zero_capacity_wave_is_rejected_on_create():
- with pytest.raises(HTTPException) as exc_info:
- _validate_wave_capacity(_create([_wave("early", 5), _wave("late", 0)]))
- assert "late" in _detail(exc_info)
-
-
-def test_event_submitted_without_waves_is_checked_through_its_primary_wave():
- """No waves means the event gets a synthesized primary wave seeded from
- `amount_tickets`, so the rule must see that rather than an empty list."""
- _validate_wave_capacity(_create(waves=None, amount_tickets=10))
-
- with pytest.raises(HTTPException) as exc_info:
- _validate_wave_capacity(_create(waves=None, amount_tickets=0))
- assert "Primary wave" in _detail(exc_info)
-
-
-def test_selling_a_wave_out_does_not_block_later_edits():
- """Zero is where a wave legitimately ends up. Rejecting it on edit would
- make a sold-out event uneditable."""
- sold_out = _wave("early", 0)
- existing = _stored([sold_out, _wave("late", 10)])
-
- _validate_wave_capacity(_create([sold_out, _wave("late", 10)]), existing)
-
-
-def test_new_wave_added_by_an_edit_still_needs_capacity():
- existing = _stored([_wave("early", 5)])
-
- with pytest.raises(HTTPException) as exc_info:
- _validate_wave_capacity(
- _create([_wave("early", 5), _wave("brand-new", 0)]), existing
- )
- assert "brand-new" in _detail(exc_info)
-
-
-def test_legacy_zero_capacity_event_stays_editable():
- """An event stored before the rule existed keeps its primary wave id, so
- an edit is not blocked — otherwise the only way to fix it would be
- barred."""
- existing = _stored([_wave("primary", 0)])
- _validate_wave_capacity(_create([_wave("primary", 0)]), existing)
diff --git a/views_api.py b/views_api.py
index 91a03f3..8a79af9 100644
--- a/views_api.py
+++ b/views_api.py
@@ -328,37 +328,6 @@ async def api_get_event(event_id: str) -> Event:
return event
-def _validate_wave_capacity(data: CreateEvent, existing: Event | None = None) -> None:
- """Every ticket wave must state a real capacity.
-
- "Capacity is always required, there is no unlimited" (#34, #62) was
- settled when capacity was a single number on the event. Since v1.6.8 it
- is per-wave and `event.amount_tickets` is a derived roll-up, so the rule
- has to be enforced where the organiser actually sets it — otherwise it
- only survives as a `min="1"` on one HTML input, which no API client is
- bound by.
-
- A zero-capacity wave can never be active (`get_active_ticket_waves`
- requires `amount_tickets > 0`), so it is the wave-level form of exactly
- what #62 removed: an event that looks on sale but refuses every
- purchase.
-
- Selling out is the one legitimate route to zero, so an edit only checks
- waves that are NEW to the event; waves already stored keep whatever
- sales decremented them to. `ensure_ticket_waves` is used rather than the
- raw list so an event submitted with no waves is checked through the
- primary wave it will be given.
- """
- known = {wave.id for wave in (existing.extra.ticket_waves if existing else [])}
- for wave in ensure_ticket_waves(data):
- if wave.id in known or wave.amount_tickets >= 1:
- continue
- raise HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail=f"Ticket wave '{wave.title}' needs a capacity of at least 1.",
- )
-
-
@events_api_router.post("")
async def api_event_create(
data: CreateEvent,
@@ -372,8 +341,6 @@ async def api_event_create(
if not data.wallet:
data.wallet = wallet.wallet.id
- _validate_wave_capacity(data)
-
from lnbits.settings import settings
ext_settings = await get_settings()
@@ -416,8 +383,6 @@ async def api_event_update(
if event.wallet != wallet.wallet.id:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
- _validate_wave_capacity(data, event)
-
from lnbits.settings import settings
ext_settings = await get_settings()