feat: event proposal and approval workflow #9
4 changed files with 48 additions and 15 deletions
feat: add location and categories fields, simplify event creation
Some checks failed
lint.yml / feat: add location and categories fields, simplify event creation (pull_request) Failing after 0s
Some checks failed
lint.yml / feat: add location and categories fields, simplify event creation (pull_request) Failing after 0s
- Add location (text) and categories (JSON list) to Event model - Make most CreateEvent fields optional: only title + start date required - Default end_date to start_date, closing_date to end_date - Categories stored as JSON text, parsed via validator - NIP-52 publisher includes location tag and t tags for categories - Migration m011 adds location and categories columns Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
commit
29045163a3
6
crud.py
6
crud.py
|
|
@ -164,6 +164,12 @@ async def purge_unpaid_tickets(event_id: str) -> None:
|
||||||
|
|
||||||
async def create_event(data: CreateEvent) -> Event:
|
async def create_event(data: CreateEvent) -> Event:
|
||||||
event_id = urlsafe_short_hash()
|
event_id = urlsafe_short_hash()
|
||||||
|
# Default end date to start date if not provided
|
||||||
|
if not data.event_end_date:
|
||||||
|
data.event_end_date = data.event_start_date
|
||||||
|
# Default closing date to end date if not provided
|
||||||
|
if not data.closing_date:
|
||||||
|
data.closing_date = data.event_end_date
|
||||||
event = Event(id=event_id, time=datetime.now(timezone.utc), **data.dict())
|
event = Event(id=event_id, time=datetime.now(timezone.utc), **data.dict())
|
||||||
await db.insert("events.events", event)
|
await db.insert("events.events", event)
|
||||||
return event
|
return event
|
||||||
|
|
|
||||||
|
|
@ -231,3 +231,15 @@ async def m010_add_events_settings(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"INSERT OR IGNORE INTO events.settings (id, auto_approve) VALUES (1, FALSE);"
|
"INSERT OR IGNORE INTO events.settings (id, auto_approve) VALUES (1, FALSE);"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m011_add_location_and_categories(db):
|
||||||
|
"""
|
||||||
|
Add location and categories columns for NIP-52 calendar event support.
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE events.events ADD COLUMN location TEXT;"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE events.events ADD COLUMN categories TEXT;"
|
||||||
|
)
|
||||||
|
|
|
||||||
39
models.py
39
models.py
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
@ -29,15 +30,17 @@ class EventExtra(BaseModel):
|
||||||
|
|
||||||
class CreateEvent(BaseModel):
|
class CreateEvent(BaseModel):
|
||||||
wallet: Optional[str] = None
|
wallet: Optional[str] = None
|
||||||
name: str
|
name: str # title (required)
|
||||||
info: str
|
info: str = "" # description (optional, visible by default)
|
||||||
closing_date: str
|
closing_date: Optional[str] = None # defaults to event_end_date or event_start_date
|
||||||
event_start_date: str
|
event_start_date: str # required
|
||||||
event_end_date: str
|
event_end_date: Optional[str] = None # defaults to event_start_date
|
||||||
currency: str = "sat"
|
currency: str = "sat"
|
||||||
amount_tickets: int = Query(..., ge=0)
|
amount_tickets: int = 0 # 0 = unlimited / not ticketed
|
||||||
price_per_ticket: float = Query(..., ge=0)
|
price_per_ticket: float = 0 # 0 = free
|
||||||
banner: Optional[str] = None
|
banner: Optional[str] = None # image URL (optional, visible by default)
|
||||||
|
location: Optional[str] = None # venue/address (optional, visible by default)
|
||||||
|
categories: list[str] = Field(default_factory=list) # NIP-52 't' tags
|
||||||
extra: EventExtra = Field(default_factory=EventExtra)
|
extra: EventExtra = Field(default_factory=EventExtra)
|
||||||
status: str = "approved" # proposed, approved, rejected
|
status: str = "approved" # proposed, approved, rejected
|
||||||
|
|
||||||
|
|
@ -67,22 +70,30 @@ class Event(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
wallet: str
|
wallet: str
|
||||||
name: str
|
name: str
|
||||||
info: str
|
info: str = ""
|
||||||
closing_date: str
|
closing_date: str | None = None
|
||||||
canceled: bool = False
|
canceled: bool = False
|
||||||
event_start_date: str
|
event_start_date: str
|
||||||
event_end_date: str
|
event_end_date: str | None = None
|
||||||
currency: str
|
currency: str = "sat"
|
||||||
amount_tickets: int
|
amount_tickets: int = 0
|
||||||
price_per_ticket: float
|
price_per_ticket: float = 0
|
||||||
time: datetime
|
time: datetime
|
||||||
sold: int = 0
|
sold: int = 0
|
||||||
banner: str | None = None
|
banner: str | None = None
|
||||||
|
location: str | None = None
|
||||||
|
categories: list[str] = Field(default_factory=list)
|
||||||
extra: EventExtra = Field(default_factory=EventExtra)
|
extra: EventExtra = Field(default_factory=EventExtra)
|
||||||
status: str = "approved" # proposed, approved, rejected
|
status: str = "approved" # proposed, approved, rejected
|
||||||
nostr_event_id: str | None = None
|
nostr_event_id: str | None = None
|
||||||
nostr_event_created_at: int | None = None
|
nostr_event_created_at: int | None = None
|
||||||
|
|
||||||
|
@validator("categories", pre=True)
|
||||||
|
def parse_categories(cls, v):
|
||||||
|
if isinstance(v, str):
|
||||||
|
return json.loads(v) if v else []
|
||||||
|
return v or []
|
||||||
|
|
||||||
|
|
||||||
class EventsSettings(BaseModel):
|
class EventsSettings(BaseModel):
|
||||||
"""Extension-level settings for the events extension."""
|
"""Extension-level settings for the events extension."""
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,17 @@ def build_nip52_event(event: Event, pubkey: str) -> NostrEvent:
|
||||||
tags.append(["end", event.event_end_date])
|
tags.append(["end", event.event_end_date])
|
||||||
if event.banner:
|
if event.banner:
|
||||||
tags.append(["image", event.banner])
|
tags.append(["image", event.banner])
|
||||||
|
if event.location:
|
||||||
|
tags.append(["location", event.location])
|
||||||
|
for cat in (event.categories or []):
|
||||||
|
tags.append(["t", cat])
|
||||||
|
|
||||||
nostr_event = NostrEvent(
|
nostr_event = NostrEvent(
|
||||||
pubkey=pubkey,
|
pubkey=pubkey,
|
||||||
created_at=int(time.time()),
|
created_at=int(time.time()),
|
||||||
kind=31922,
|
kind=31922,
|
||||||
tags=tags,
|
tags=tags,
|
||||||
content=event.info,
|
content=event.info or "",
|
||||||
)
|
)
|
||||||
nostr_event.id = nostr_event.event_id
|
nostr_event.id = nostr_event.event_id
|
||||||
return nostr_event
|
return nostr_event
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue