feat: Bandcamp/Nugs links for shows and performances
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run

- Add bandcamp_link, nugs_link to Performance model
- Admin endpoints: PATCH /admin/performances/{id}
- Bulk import: POST /admin/import/external-links
- Spec doc: docs/BANDCAMP_NUGS_SPEC.md
This commit is contained in:
fullsizemalt 2025-12-23 15:56:21 -08:00
parent 68453d6865
commit 1f29cdf290
3 changed files with 330 additions and 0 deletions

View file

@ -15,6 +15,8 @@ class Performance(SQLModel, table=True):
notes: Optional[str] = Field(default=None) notes: Optional[str] = Field(default=None)
track_url: Optional[str] = Field(default=None, description="Deep link to track audio") track_url: Optional[str] = Field(default=None, description="Deep link to track audio")
youtube_link: Optional[str] = Field(default=None, description="YouTube video URL") youtube_link: Optional[str] = Field(default=None, description="YouTube video URL")
bandcamp_link: Optional[str] = Field(default=None, description="Bandcamp track URL")
nugs_link: Optional[str] = Field(default=None, description="Nugs.net track URL")
nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance") nicknames: List["PerformanceNickname"] = Relationship(back_populates="performance")
show: "Show" = Relationship(back_populates="performances") show: "Show" = Relationship(back_populates="performances")

View file

@ -432,3 +432,114 @@ def delete_tour(
session.delete(tour) session.delete(tour)
session.commit() session.commit()
return {"message": "Tour deleted", "tour_id": tour_id} return {"message": "Tour deleted", "tour_id": tour_id}
# ============ PERFORMANCES ============
from models import Performance
class PerformanceUpdate(BaseModel):
notes: Optional[str] = None
youtube_link: Optional[str] = None
bandcamp_link: Optional[str] = None
nugs_link: Optional[str] = None
track_url: Optional[str] = None
@router.patch("/performances/{performance_id}")
def update_performance(
performance_id: int,
update: PerformanceUpdate,
session: Session = Depends(get_session),
_: User = Depends(allow_admin)
):
"""Update performance links and notes"""
performance = session.get(Performance, performance_id)
if not performance:
raise HTTPException(status_code=404, detail="Performance not found")
for key, value in update.model_dump(exclude_unset=True).items():
setattr(performance, key, value)
session.add(performance)
session.commit()
session.refresh(performance)
return performance
@router.get("/performances/{performance_id}")
def get_performance(
performance_id: int,
session: Session = Depends(get_session),
_: User = Depends(allow_admin)
):
"""Get performance details for admin"""
performance = session.get(Performance, performance_id)
if not performance:
raise HTTPException(status_code=404, detail="Performance not found")
return {
"id": performance.id,
"slug": performance.slug,
"show_id": performance.show_id,
"song_id": performance.song_id,
"position": performance.position,
"set_name": performance.set_name,
"notes": performance.notes,
"youtube_link": performance.youtube_link,
"bandcamp_link": performance.bandcamp_link,
"nugs_link": performance.nugs_link,
"track_url": performance.track_url,
}
class BulkLinksImport(BaseModel):
links: List[dict] # {"show_id": 1, "platform": "nugs", "url": "..."} or {"performance_id": 1, ...}
@router.post("/import/external-links")
def bulk_import_links(
data: BulkLinksImport,
session: Session = Depends(get_session),
_: User = Depends(allow_admin)
):
"""Bulk import external links for shows and performances"""
updated_shows = 0
updated_performances = 0
errors = []
for item in data.links:
platform = item.get("platform", "").lower()
url = item.get("url")
if not platform or not url:
errors.append({"item": item, "error": "Missing platform or url"})
continue
field_name = f"{platform}_link"
if "show_id" in item:
show = session.get(Show, item["show_id"])
if show and hasattr(show, field_name):
setattr(show, field_name, url)
session.add(show)
updated_shows += 1
else:
errors.append({"item": item, "error": "Show not found or invalid platform"})
elif "performance_id" in item:
perf = session.get(Performance, item["performance_id"])
if perf and hasattr(perf, field_name):
setattr(perf, field_name, url)
session.add(perf)
updated_performances += 1
else:
errors.append({"item": item, "error": "Performance not found or invalid platform"})
session.commit()
return {
"updated_shows": updated_shows,
"updated_performances": updated_performances,
"errors": errors
}

217
docs/BANDCAMP_NUGS_SPEC.md Normal file
View file

@ -0,0 +1,217 @@
# Bandcamp & Nugs Integration Spec
**Date:** 2023-12-23
**Purpose:** Link shows and performances to official audio sources
---
## Overview
Add support for linking to official audio releases on:
- **Bandcamp** - Official studio/live releases, digital purchases
- **Nugs.net** - Live show streams/downloads, SBD recordings
---
## Database Changes
### Option A: Simple Link Fields (MVP)
Add to existing models:
```python
# Show model
class Show(SQLModel, table=True):
# ... existing fields ...
nugs_link: Optional[str] = None # Full Nugs.net URL
bandcamp_link: Optional[str] = None # Full Bandcamp URL
# Performance model
class Performance(SQLModel, table=True):
# ... existing fields ...
nugs_link: Optional[str] = None # Link to specific track on Nugs
bandcamp_link: Optional[str] = None # Link to specific track on Bandcamp
```
### Option B: Structured Link Table (Future)
For more flexibility:
```python
class ExternalLink(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
# Polymorphic reference
entity_type: str # "show" | "performance" | "song"
entity_id: int
# Link info
platform: str # "nugs" | "bandcamp" | "youtube" | "archive" | "spotify"
url: str
label: Optional[str] = None # Custom label like "SBD Recording"
is_official: bool = True
created_at: datetime
created_by: Optional[int] # User who added it
```
---
## API Endpoints
### Update Show (Admin)
```
PATCH /admin/shows/{id}
{
"nugs_link": "https://nugs.net/...",
"bandcamp_link": "https://bandcamp.com/..."
}
```
### Update Performance (Admin)
```
PATCH /admin/performances/{id}
{
"nugs_link": "https://nugs.net/...",
"bandcamp_link": "https://bandcamp.com/..."
}
```
### Bulk Import Links
```
POST /admin/import/external-links
{
"links": [
{"show_id": 123, "platform": "nugs", "url": "..."},
{"performance_id": 456, "platform": "bandcamp", "url": "..."}
]
}
```
---
## Frontend Display
### Show Page
```
┌────────────────────────────────────────────┐
│ 📅 December 13, 2025 @ The Anthem │
│ │
│ [▶ Watch on YouTube] [🎧 Nugs] [🎵 Bandcamp]│
│ │
│ Set 1: │
│ 1. Song Title [🎧] [🎵] ← per-track │
│ 2. Another Song │
└────────────────────────────────────────────┘
```
### Icon/Button Design
```tsx
const PLATFORM_ICONS = {
nugs: { icon: Headphones, label: "Nugs.net", color: "#ff6b00" },
bandcamp: { icon: Music, label: "Bandcamp", color: "#629aa9" },
youtube: { icon: Youtube, label: "YouTube", color: "#ff0000" },
}
```
---
## Data Sources
### Nugs.net
- Shows listed at: <https://nugs.net/artist/>...
- Direct track links available
- May have SBD vs AUD quality indicators
### Bandcamp
- Live releases often on artist's Bandcamp
- Track-level linking possible
- May include "pay what you want" vs fixed price
---
## Import Workflow
### Manual Entry (Admin UI)
1. Admin navigates to show/performance
2. Clicks "Add External Links"
3. Pastes URL, selects platform
4. Saves
### Bulk CSV Import
```csv
show_date,platform,url
2024-12-13,nugs,https://nugs.net/live/...
2024-12-13,bandcamp,https://bandcamp.com/album/...
```
### API Scraping (Future)
- Could auto-detect new releases on Nugs
- Match by date/venue
- Requires API access or web scraping
---
## Implementation Phases
### Phase 1: MVP (Database + Admin)
- [ ] Add `nugs_link`, `bandcamp_link` to Show model
- [ ] Add `nugs_link`, `bandcamp_link` to Performance model
- [ ] Run migration
- [ ] Add admin endpoints to update links
### Phase 2: Frontend Display
- [ ] Show links on Show page (next to YouTube)
- [ ] Show links on Performance rows
- [ ] Add platform icons/colors
### Phase 3: Import Tools
- [ ] CSV import endpoint
- [ ] Admin bulk edit UI
### Phase 4: User Features (Optional)
- [ ] Users can suggest links (moderated)
- [ ] "I own this" tracking for collectors
---
## Estimated Effort
| Phase | Time |
|-------|------|
| Phase 1 | 30 min |
| Phase 2 | 1 hour |
| Phase 3 | 1 hour |
| Phase 4 | 2+ hours |
---
## Questions to Resolve
1. **Should users be able to add links?** Or admin-only?
2. **Verify link quality?** Some Nugs links are AUD, some SBD
3. **Other platforms?** Spotify, Apple Music, Archive.org, Relisten?
4. **Affiliate links?** If monetizing, need affiliate program setup
---
## Next Steps
1. Run database migration
2. Add admin API endpoints
3. Update Show page UI with link buttons