Album

The release container. Every Track belongs to exactly one Album — a single is just an Album with one track. The Album owns release metadata, copyright, AI disclosure, label affiliation, cover artwork and contributor credits.

apps/api/src/Features/Albums/Models/Album.cs

1 Full schema

Album inherits from BaseModel (Id, CreatedAtUtc, UpdatedAtUtc) and is mapped to the albums table.

public class Album : BaseModel
{
    public required string Title { get; set; }

    public AlbumStatus Status { get; set; } = AlbumStatus.Draft;
    public DateTime? PublishedAtUtc { get; set; }

    public ICollection<Artist> Artists { get; set; } = [];
    public ICollection<Track>  Tracks  { get; set; } = [];

    // Editorial content
    public string? Description { get; set; }
    public string? EditorialNotes { get; set; }
    public string  DefaultLocale { get; set; } = "en";

    // Type / format
    public ReleaseType Type { get; set; } = ReleaseType.Single;

    // Dates
    public DateTime ReleaseDate { get; set; }
    public DateTimePrecision ReleaseDatePrecision { get; set; } = DateTimePrecision.Year;
    public DateTime? OriginalReleaseDate { get; set; }
    public bool IsReissue { get; set; }

    // Denormalized
    public int TotalTracks { get; set; }

    // Copyright
    public int?    PLineYear { get; set; }
    public string? PLineHolder { get; set; }
    public int?    CLineYear { get; set; }
    public string? CLineHolder { get; set; }

    // AI disclosure
    public bool AiGenerated { get; set; } = true;
    public AiDisclosureLevel AiDisclosure { get; set; } = AiDisclosureLevel.FullyAiGenerated;
    public string? AiToolUsed { get; set; }
    public string? HumanContributionDescription { get; set; }

    // Label
    public Guid?  LabelId { get; set; }
    public Label? Label { get; set; }

    // Identifiers
    public string? Upc { get; set; }
    public string? CatalogNumber { get; set; }

    // Cover meta
    public Guid? PrimaryCoverImageId { get; set; }
    public AlbumImage? PrimaryCoverImage { get; set; }
    public string? DominantColor { get; set; }
    public string? PaletteJson { get; set; }

    // Type-specific optionals
    public string? VenueName { get; set; }
    public DateTime? RecordedAtUtc { get; set; }
    public string? RelatedTitle { get; set; }
    public Guid?  OriginalReleaseId { get; set; }
    public Album? OriginalRelease { get; set; }

    // Additional navigations
    public ICollection<AlbumImage>       Images       { get; set; } = [];
    public ICollection<AlbumContributor> Contributors { get; set; } = [];
    public ICollection<Mood>             Moods        { get; set; } = [];
}

2 Identity & status

FieldTypeDescription
Id Guid Primary key. Inherited from BaseModel.
Title string · required · ≤255 Album title.
Status AlbumStatus enum Lifecycle state. Default Draft. See §9.
PublishedAtUtc DateTime? When the album was published. null while a draft.
TotalTracks int Denormalised track count, kept in sync with the Tracks collection to avoid a join on listings.

3 Editorial

FieldTypeDescription
Description string? · ≤500 Short public description shown on the album page.
EditorialNotes string? · text Longer internal/editorial notes (no length cap).
DefaultLocale string · ≤8 · default "en" Default locale for the album's textual metadata, used when a localised variant is missing.

4 Type & dates

FieldTypeDescription
Type ReleaseType enum Release format (Single, EP, Album, Compilation, …). Default Single. Indexed. See §9.
ReleaseDate DateTime The release date on Beatix. Indexed.
ReleaseDatePrecision DateTimePrecision enum How precise ReleaseDate is — Year, YearMonth or Day. Lets the UI render "2026" vs "May 2026" vs a full date.
OriginalReleaseDate DateTime? The original release date for reissues/remasters, when it differs from ReleaseDate.
IsReissue bool Flags the album as a reissue of an earlier release.

5 Commercial & identifiers

Copyright fields here are the defaults for the album's tracks. A Track may override any of them via its *Override fields — see Track · Commercial.

FieldTypeDescription
PLineYear / PLineHolder int? / string? ≤255 Recording rights (℗) — year and holder.
CLineYear / CLineHolder int? / string? ≤255 Composition rights (©) — year and holder.
Upc string? · ≤13 Universal Product Code for the release. A unique, filtered index enforces uniqueness when not null.
CatalogNumber string? · ≤80 The label's internal catalog number for the release.
LabelId / Label Guid? + nav Optional FK to the releasing Label. OnDelete = SetNull.

6 AI disclosure

Beatix is an AI-music platform, so AI provenance is first-class metadata. By default a new album is treated as fully AI-generated.

FieldTypeDescription
AiGenerated bool · default true Whether any part of the release was AI-generated.
AiDisclosure AiDisclosureLevel enum The disclosure level — NotAiGenerated, AiAssisted, FullyAiGenerated or AiCover. See §9.
AiToolUsed string? · ≤80 Name of the AI tool/model used (e.g. "Suno v4", "Udio").
HumanContributionDescription string? · text Free-text description of the human contribution (writing, production, performance) for the AiAssisted case.

7 Cover & artwork

FieldTypeDescription
PrimaryCoverImageId / PrimaryCoverImage Guid? + AlbumImage? FK to the chosen primary cover variant. OnDelete = SetNull.
DominantColor string? · ≤7 Dominant cover colour as a hex string (e.g. #1a2342), used for theming.
PaletteJson string? · jsonb Extracted colour palette stored as JSON, for gradient/UI accents.

The actual image files and their resized variants live in AlbumImage rows. The image generation and format strategy is documented in docs/image-format-strategy.md.

8 Type-specific fields

These optionals are only meaningful for certain ReleaseType values. They are nullable and ignored otherwise.

FieldTypeUsed byDescription
VenueName string? ≤200 Live Where the live album was recorded.
RecordedAtUtc DateTime? Live When the live recording took place.
RelatedTitle string? ≤200 Soundtrack The film/show/game the soundtrack belongs to.
OriginalReleaseId / OriginalRelease Guid? + self-nav Remix Self-reference to the album this one remixes. OnDelete = SetNull.

9 Enums

AlbumStatus

ValueIntMeaning
Draft0Being created; not public. Default.
Published1Visible in the public catalog.
Archived2Removed from the catalog but preserved.
Deleted3Soft-deleted.

ReleaseType

ValueIntMeaning
Single0One or a few tracks. The default.
EP1Extended play — more than a single, shorter than an album.
Album2Full-length album.
Compilation3Curated collection of pre-existing tracks.
Soundtrack4Soundtrack — pairs with RelatedTitle.
Live5Live release — pairs with VenueName / RecordedAtUtc.
Mixtape6Mixtape.
Remix7Remix release — pairs with OriginalReleaseId.
Demo8Demo release.
GeneratedPack100Beatix-specific: a batch-generated pack of AI tracks.
AiSampler101Beatix-specific: an AI sampler release.

DateTimePrecision

ValueIntMeaning
Year0Only the year is known. The default.
YearMonth1Year and month are known.
Day2The full date is known.

AiDisclosureLevel

ValueIntMeaning
NotAiGenerated0Entirely human-made.
AiAssisted1Human-led with AI assistance — describe in HumanContributionDescription.
FullyAiGenerated2Fully AI-generated. The default for new albums.
AiCover3An AI-generated cover of an existing work.

10 AlbumContributor

A rich association between an Album and an Artist, carrying a credit role. This is album-level credits; track-level credits use the parallel TrackContributor.

apps/api/src/Features/Albums/Models/AlbumContributor.cs
FieldTypeDescription
AlbumId / Album Guid + nav Parent album. OnDelete = Cascade.
ArtistId / Artist Guid + nav The credited artist. OnDelete = Restrict.
Role ContributorRole enum The credited role. See the ContributorRole reference.
RoleDetail string? · ≤120 Free-text refinement of the role.
Instrument string? · ≤80 Instrument played, when relevant.
CreditedAs string? · ≤255 The name to display in credits, if different from the artist's canonical name.
IsPrimary bool Whether this is a primary credit (vs a secondary one).
Order int Display order. Indexed together with AlbumId.

11 AlbumImage

A single resized variant of an album's artwork. An album has one AlbumImage row per (Variant, Size) pair. AlbumImage implements the shared IImageVariantRecord interface.

apps/api/src/Features/Albums/Models/AlbumImage.cs
FieldTypeDescription
AlbumId / Album Guid + nav Parent album. OnDelete = Cascade.
Variant ImageVariant enum Crop/shape — Square, Banner, Hero, Portrait. See Image variants.
Size ImageSize enum Resolution tier — Thumb, Small, Medium, Large, Original.
ObjectKey string · required · ≤500 S3 object key of the stored file.
Width / Height int Pixel dimensions of this variant.
SizeBytes long File size in bytes.
ContentHash string · required · ≤64 Content hash for dedup/cache-busting. Indexed.
IsPrimary bool Marks the primary image for its (Variant, Size). A unique filtered index (ix_album_images_primary_unique) allows only one primary per group.

12 Relationships

Property Cardinality Join / FK OnDelete
Artists Many-to-Many album_artists Cascade (join table)
Tracks One-to-Many tracks.AlbumId Restrict
Moods Many-to-Many album_moods Cascade (join table)
Images One-to-Many album_images.AlbumId Cascade
Contributors One-to-Many album_contributors.AlbumId Cascade
Label Many-to-One (optional) albums.LabelId SetNull
PrimaryCoverImage Many-to-One (optional) albums.PrimaryCoverImageId SetNull
OriginalRelease Self-reference (optional) albums.OriginalReleaseId SetNull
Restrict on Tracks

An album cannot be deleted while it still has tracks — the fk_tracks_album FK uses Restrict. Delete or move the tracks first. This protects against accidentally wiping a release.