feat: Bandcamp/Nugs links for shows and performances
Some checks are pending
Deploy Elmeg / deploy (push) Waiting to run
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:
parent
68453d6865
commit
1f29cdf290
3 changed files with 330 additions and 0 deletions
|
|
@ -15,6 +15,8 @@ class Performance(SQLModel, table=True):
|
|||
notes: Optional[str] = Field(default=None)
|
||||
track_url: Optional[str] = Field(default=None, description="Deep link to track audio")
|
||||
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")
|
||||
show: "Show" = Relationship(back_populates="performances")
|
||||
|
|
|
|||
|
|
@ -432,3 +432,114 @@ def delete_tour(
|
|||
session.delete(tour)
|
||||
session.commit()
|
||||
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
217
docs/BANDCAMP_NUGS_SPEC.md
Normal 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
|
||||
Loading…
Add table
Reference in a new issue