"""Collaborator handlers.""" from typing import List, Optional from flask.typing import ResponseReturnValue from flask_pydantic import validate from collaborator.api import app from collaborator.constants import error from collaborator.constants.features import BULK_SPLIT_INGESTION from collaborator.constants.split_type import SplitTypeId from collaborator.logic import split from collaborator.logic.bulk_ingest_splits import run_bulk_ingest from collaborator.logic.bulk_ingest_template import get_bulk_ingest_template from collaborator.schemas import BaseSchema from collaborator.schemas.split import ( BulkSplitRow, ReplaceSplitsBody, ReplaceSplitsRequestSchema, ) from collaborator.utils import features from collaborator.utils import handlers as handler_utils from collaborator.utils.error import OwsError class SplitsDataloaderBody(BaseSchema): """Body parameters for POST /splits/dataloader.""" track_ids: List[int] @app.route("/splits/dataloader", methods=["POST"]) @validate() @handler_utils.fetch_authorized_resources def get_splits_dataloader( authorized_resources, user, body: SplitsDataloaderBody ) -> ResponseReturnValue: """Get the list of splits for the specified account and list of identifiers. Args: authorized_resources (List[Resource]): Resources which this user is authorized to access. Returns: flask.Response: list of splits """ track_ids = [str(tuid) for tuid in body.track_ids] split.check_can_access_splits_data(authorized_resources, track_ids) splits_grouped_by_tuid = split.get_for_identifiers(SplitTypeId.TRACK, track_ids) return [{"data": splits_grouped_by_tuid.get(str(ident))} for ident in track_ids] @app.route("/splits/replace", methods=["PUT"]) @validate() @handler_utils.fetch_authorized_resources def replace_track_splits( authorized_resources, user, body: ReplaceSplitsBody ) -> ResponseReturnValue: """Update the list of splits for the specified split id. Args: authorized_resources (AuthorizedResources): Resources which this user is authorized to access. user (User): User who is replacing the splits. Returns: flask.Response: list of replaced splits """ return split.replace_track_splits(authorized_resources, body, user) @app.route("/splits", methods=["PUT"]) @validate() @handler_utils.fetch_authorized_resources def replace_splits( authorized_resources, user, body: ReplaceSplitsRequestSchema ) -> ResponseReturnValue: """Update the list of splits for the specified split id. Args: authorized_resources (AuthorizedResources): Resources which this user is authorized to access. user (User): User who is replacing the splits. Returns: flask.Response: list of replaced splits """ return split.replace_splits(authorized_resources, body, user) class BulkIngestSplitsBody(BaseSchema): """Request body for bulk ingest splits. Attributes: splits_config (list): The list of split rows to ingest. dry_run (bool): If True (default), validate and return summary without writing. ticket_id (str): Required when dry_run is False; used as created_by/updated_by. """ splits_config: list[BulkSplitRow] dry_run: bool = True ticket_id: Optional[str] = None class BulkIngestTemplateQuery(BaseSchema): """Query params for the bulk ingest template endpoint.""" vendor_id: int @app.route("/splits/bulk-ingest-template", methods=["GET"]) @validate() @handler_utils.fetch_authorized_resources def get_bulk_ingest_template_handler( authorized_resources, user, query: BulkIngestTemplateQuery ): """Return the bulk split ingestion template for a vendor. Returns all tracks for the vendor with their existing split configuration. Tracks without splits are included as empty rows. Args: authorized_resources (AuthorizedResources): Resources which this user is authorized to access. Returns: flask.Response: list of template rows """ if not features.is_feature_enabled(BULK_SPLIT_INGESTION): raise OwsError( code=error.ERROR_CODE_AUTHORIZATION, message=error.ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) return [row.dump() for row in get_bulk_ingest_template(query.vendor_id)] @app.route("/splits/bulk-ingest", methods=["POST"]) @validate() @handler_utils.fetch_authorized_resources def bulk_ingest_splits(authorized_resources, user, body: BulkIngestSplitsBody): """Bulk ingest splits for multiple tracks. Accepts a dry_run flag (default True). When dry_run=True the endpoint validates the payload and returns an IngestSummary without writing to the DB. When dry_run=False a ticket_id is required and changes are persisted. Args: authorized_resources (AuthorizedResources): Resources which this user is authorized to access. Returns: flask.Response: IngestSummary with counts of what was (or would be) changed. """ if not features.is_feature_enabled(BULK_SPLIT_INGESTION): raise OwsError( code=error.ERROR_CODE_AUTHORIZATION, message=error.ERROR_MESSAGE_FORBIDDEN_USER, status=403, ) try: summary = run_bulk_ingest( splits_config=body.splits_config, dry_run=body.dry_run, ticket_id=body.ticket_id, ) except ValueError as e: raise OwsError( code=error.ERROR_CODE_BAD_PARAMS, message=str(e), ) from e return summary