- Remove global Nextcloud settings from config - Make NextcloudClient require explicit credentials - Update for_member() to return None when no credentials - Modify services to accept optional storage client - Update routers to pass member storage to services - Add 403 responses when no storage provider configured - Update internal endpoints to use member storage credentials This change enforces that each member must configure their own Nextcloud storage provider. If no provider is configured, file operations will return 403 FORBIDDEN instead of falling back to global placeholders.
93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from rehearsalhub.db.models import AudioVersion, Song
|
|
from rehearsalhub.queue.redis_queue import RedisJobQueue
|
|
from rehearsalhub.repositories.audio_version import AudioVersionRepository
|
|
from rehearsalhub.repositories.song import SongRepository
|
|
from rehearsalhub.schemas.audio_version import AudioVersionCreate
|
|
from rehearsalhub.schemas.song import SongCreate, SongRead, SongUpdate
|
|
from rehearsalhub.storage.nextcloud import NextcloudClient
|
|
|
|
|
|
class SongService:
|
|
def __init__(
|
|
self,
|
|
session: AsyncSession,
|
|
job_queue: RedisJobQueue | None = None,
|
|
storage: NextcloudClient | None = None,
|
|
) -> None:
|
|
self._repo = SongRepository(session)
|
|
self._version_repo = AudioVersionRepository(session)
|
|
self._session = session
|
|
self._queue = job_queue or RedisJobQueue(session)
|
|
self._storage = storage
|
|
|
|
async def create_song(
|
|
self, band_id: uuid.UUID, data: SongCreate, creator_id: uuid.UUID, band_slug: str,
|
|
creator: object | None = None,
|
|
) -> Song:
|
|
from rehearsalhub.storage.nextcloud import NextcloudClient
|
|
nc_folder = f"bands/{band_slug}/songs/{data.title.lower().replace(' ', '-')}/"
|
|
storage = NextcloudClient.for_member(creator) if creator else self._storage
|
|
try:
|
|
await storage.create_folder(nc_folder)
|
|
except Exception:
|
|
nc_folder = None # best-effort
|
|
|
|
song = await self._repo.create(
|
|
band_id=band_id,
|
|
title=data.title,
|
|
status=data.status,
|
|
notes=data.notes,
|
|
nc_folder_path=nc_folder,
|
|
created_by=creator_id,
|
|
)
|
|
return song
|
|
|
|
async def list_songs(self, band_id: uuid.UUID) -> list[SongRead]:
|
|
songs = await self._repo.list_for_band(band_id)
|
|
result = []
|
|
for song in songs:
|
|
versions = song.versions
|
|
read = SongRead.model_validate(song)
|
|
read.version_count = len(versions)
|
|
if versions:
|
|
latest = max(versions, key=lambda v: v.version_number)
|
|
read.latest_version_id = latest.id
|
|
result.append(read)
|
|
return result
|
|
|
|
async def register_version(
|
|
self,
|
|
song_id: uuid.UUID,
|
|
data: AudioVersionCreate,
|
|
uploader_id: uuid.UUID,
|
|
) -> AudioVersion:
|
|
if data.nc_file_etag:
|
|
existing = await self._version_repo.get_by_etag(data.nc_file_etag)
|
|
if existing:
|
|
return existing
|
|
|
|
version_number = await self._repo.next_version_number(song_id)
|
|
version = await self._version_repo.create(
|
|
song_id=song_id,
|
|
version_number=version_number,
|
|
nc_file_path=data.nc_file_path,
|
|
nc_file_etag=data.nc_file_etag,
|
|
label=data.label,
|
|
format=data.format,
|
|
file_size_bytes=data.file_size_bytes,
|
|
analysis_status="pending",
|
|
uploaded_by=uploader_id,
|
|
)
|
|
|
|
await self._queue.enqueue(
|
|
"transcode",
|
|
{"version_id": str(version.id), "nc_file_path": data.nc_file_path},
|
|
)
|
|
return version
|