Remove YouTube integration from backend and deployment config

Drops the YouTube-backed second catalog source entirely: the
Local/External split on Track/Album collapses into single concrete
entities, Artist/TrackArtist/ExternalTrack/ExternalAlbum and the
YouTube service/download pipeline (yt-dlp, MassTransit consumer,
routing slip) are deleted, and mixes now source discovery only from
local listening history. Includes the RemoveYouTubeIntegration EF
Core migration and matching Worker/deploy/compose cleanup (no more
yt-dlp/python/deno in the Worker image, no thumbnail-cache/YtDlp
config sections).
This commit is contained in:
2026-09-15 21:17:03 +02:00
parent 5696683859
commit 31032bd8e8
137 changed files with 1933 additions and 3969 deletions
@@ -1,24 +1,11 @@
using System.Text.Json.Serialization;
using Musify.Application.Serialization;
using Musify.Domain.Entities;
using Musify.Domain.ValueObjects;
using System.Text.Json.Serialization;
namespace Musify.Application.Tracks.Responses
{
// See AlbumApplicationResponse for why [JsonPolymorphic]/[JsonDerivedType] are required
// (not optional) whenever a base type carries subtype-only data across the wire. Track is a
// cleaner case than Album: both OwnerUserId (local-only) and ExternalId (external-only) are
// genuinely exclusive to one side, so the base has no "meaningless sentinel" field at all —
// every real value is one of the two derived types, hence base is abstract.
//
// No explicit Source property here (unlike the domain entities): STJ's polymorphism support
// already injects a "source" discriminator property with the same "Local"/"YouTube" values a
// hand-written TrackSource field would have, and a real property with that exact name would
// collide with it ("contains property 'source' that conflicts with an existing metadata
// property name"). Use the discriminator (or a type check) instead of a Source field.
[JsonPolymorphic(TypeDiscriminatorPropertyName = "source")]
[JsonDerivedType(typeof(LocalTrackApplicationResponse), typeDiscriminator: "Local")]
[JsonDerivedType(typeof(ExternalTrackApplicationResponse), typeDiscriminator: "YouTube")]
public abstract record TrackApplicationResponse(
public record TrackApplicationResponse(
Guid Id,
string Title,
string? Artist,
@@ -26,24 +13,19 @@ namespace Musify.Application.Tracks.Responses
double Duration,
int ListensCount,
DateTime CreatedAt,
DateTime UpdatedAt)
DateTime UpdatedAt,
[property: JsonConverter(typeof(LongAsStringConverter))] long OwnerUserId)
{
public static TrackApplicationResponse FromEntity(Track track, int listensCount) =>
track switch
{
LocalTrack local => LocalTrackApplicationResponse.FromEntity(local, listensCount),
ExternalTrack external => ExternalTrackApplicationResponse.FromEntity(external, listensCount),
_ => throw new NotSupportedException($"Unsupported track entity type '{track.GetType().Name}'.")
};
protected static string? FormatArtist(Track track) => track switch
{
LocalTrack local => local.Owner.Name,
ExternalTrack { TrackArtists.Count: > 0 } external =>
string.Join(", ", external.TrackArtists
.OrderBy(trackArtist => trackArtist.Position)
.Select(trackArtist => trackArtist.Artist.Name)),
_ => null
};
new(
track.Id,
track.Title,
track.Owner.Name,
track.Audio.TranscodeStatus,
track.DurationSeconds,
listensCount,
track.CreatedAt,
track.UpdatedAt,
track.OwnerUserId);
}
}