"""TrackAdditionalIsrc model + dual-write mirror helpers. A track's ISRC for an additional (non-stereo) recording, discriminated by a ``type`` enum (``atmos`` today; ``sony360`` / ``5.1`` later) -- the replacement for the one-per-track ``track_spatial`` table. During the migration ows-track dual-writes: every ``track_spatial`` write is mirrored here so this table is ready for the later reader cutover. While ``track_spatial`` stays authoritative the new table simply tracks it, so the mirror helpers set the ISRC and deleted_at to match the old row. The dual-write only ever writes ``atmos`` (it mirrors ``track_spatial``, the atmos ISRC); other formats are written directly by the type-aware endpoints once this table is authoritative. The type-aware authoritative semantics (409 on a second active recording, same-ISRC-on-reupload) likewise arrive with those endpoints. """ from enum import StrEnum from sqlalchemy import Column from sqlalchemy import DateTime from sqlalchemy import Enum from sqlalchemy import ForeignKey from sqlalchemy import func from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import UniqueConstraint from oto import response from backend.connectors import mysql class TrackAdditionalIsrcType(StrEnum): """Format of an additional (non-stereo) recording. Add a member to extend the set; also append the value to the DB enum (``ALTER TABLE ... MODIFY type ENUM(...)`` is ALGORITHM=INPLACE when appending). StrEnum members compare equal to their string value, so they interoperate with the raw ``'atmos'`` token used across the system. """ ATMOS = 'atmos' class TrackAdditionalIsrc(mysql.BaseModel): """A track's ISRC for one additional (non-stereo) recording, keyed by type.""" __tablename__ = 'track_additional_isrc' __table_args__ = ( UniqueConstraint( 'track_id', 'type', name='uq_track_additional_isrc_track_type'), ) id = Column(Integer, primary_key=True, autoincrement=True, nullable=False) track_id = Column( Integer, ForeignKey('track.id', ondelete='CASCADE'), nullable=False) # values_callable stores the enum *values* ('atmos'), not the member names # ('ATMOS'), so the column matches the deployed ENUM('atmos') DDL. type = Column( Enum( TrackAdditionalIsrcType, values_callable=lambda enum_cls: [m.value for m in enum_cls]), nullable=False) isrc = Column(String(16), nullable=False) created_at = Column(DateTime, default=func.now()) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) deleted_at = Column(DateTime, nullable=True) def mirror_create_or_restore(spatial_data, session, deleted_at=None): """Mirror a track_spatial create/upsert onto track_additional_isrc. Ensures the track's (track, atmos) row carries the given ISRC and the same deleted_at state as the just-written track_spatial row -- callers pass the old row's deleted_at so the mirror tracks it exactly (e.g. an upsert against a soft-deleted track_spatial row keeps the mirror soft-deleted too). Updates the existing row or inserts when none exists. Operates on the caller's session and does NOT commit, so the dual-write is one atomic transaction with the track_spatial write. """ record = session.query(TrackAdditionalIsrc).filter( TrackAdditionalIsrc.track_id == spatial_data['track_id'], TrackAdditionalIsrc.type == TrackAdditionalIsrcType.ATMOS, ).one_or_none() if record: record.isrc = spatial_data['isrc'] record.deleted_at = deleted_at return session.add(TrackAdditionalIsrc( track_id=spatial_data['track_id'], type=TrackAdditionalIsrcType.ATMOS, isrc=spatial_data['isrc'], deleted_at=deleted_at)) def mirror_soft_delete(tuid, session): """Mirror a track_spatial delete by soft-deleting the active atmos row. No-op when the track has no active atmos row. Operates on the caller's session and does NOT commit. """ record = session.query(TrackAdditionalIsrc).filter( TrackAdditionalIsrc.track_id == tuid, TrackAdditionalIsrc.type == TrackAdditionalIsrcType.ATMOS, TrackAdditionalIsrc.deleted_at.is_(None), ).one_or_none() if record: record.deleted_at = func.now() @mysql.wrap_db_errors @mysql.db_session_wrap def get_atmos_isrc(track_id, session): """Return a Response whose message is the track's atmos ISRC, or None. Lets create/update_track_spatial reuse a track's permanent atmos ISRC rather than claiming a fresh one when the track already has one. Finds the row regardless of deleted_at (the ISRC is permanent), and returns a Response so a DB error surfaces as a fatal response rather than being read as "no ISRC". """ record = session.query(TrackAdditionalIsrc).filter( TrackAdditionalIsrc.track_id == track_id, TrackAdditionalIsrc.type == TrackAdditionalIsrcType.ATMOS, ).one_or_none() return response.Response(message=record.isrc if record else None)