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)
|
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")
|
||||||
|
|
|
||||||
|
|
@ -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
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