feat(core): add Author and Series entities for hierarchical monitoring (#118)

* feat(db): add MediaType discriminator to Movies table

Adds foundation for multi-media support by:
- Adding MediaType column to Movies table (migration 244)
- Adding MediaType property to Movie entity, defaulting to Movie

Existing movies will have MediaType=1 (Movie) after migration.
This prepares for future Book and Audiobook media types.

Addresses Issue #1

* refactor(core): create MediaItem abstract base class

Extracts common properties from Movie into a new MediaItem base class:
- MediaType, Monitored, QualityProfileId
- Path, RootFolderPath, Added, Tags, LastSearchTime

Movie now inherits from MediaItem and implements abstract methods
GetTitle() and GetYear() while maintaining backward-compatible
Title/Year property accessors.

This prepares for Book and Audiobook entities that will share
the same base structure.

Addresses Issue #1

* feat(core): add Author and Series entities for hierarchical monitoring

Introduces hierarchical structure for books/audiobooks:
- Author entity: tracks authors with monitoring, quality profiles, paths
- Series entity: groups books/audiobooks by series, linked to Author
- MediaItem: adds AuthorId and SeriesId for hierarchy support
- Migration 245: creates Authors and Series tables, adds columns to Movies

This enables Author → Series → Item monitoring inheritance for
future book and audiobook support.

Addresses Issue #2

---------

Co-authored-by: admin <admin@ardentleatherworks.com>
This commit is contained in:
Cody Kickertz 2025-12-21 19:50:55 -06:00 committed by GitHub
parent 10c333a7d3
commit 5ac92d610a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 93 additions and 0 deletions

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Authors
{
public class Author : ModelBase
{
public Author()
{
Tags = new HashSet<int>();
}
public string Name { get; set; }
public string SortName { get; set; }
public string Description { get; set; }
public string ForeignAuthorId { get; set; }
public bool Monitored { get; set; }
public string Path { get; set; }
public string RootFolderPath { get; set; }
public int QualityProfileId { get; set; }
public DateTime Added { get; set; }
public HashSet<int> Tags { get; set; }
public override string ToString()
{
return Name;
}
}
}

View file

@ -0,0 +1,36 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(245)]
public class add_author_series_tables : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Create.TableForModel("Authors")
.WithColumn("Name").AsString().NotNullable()
.WithColumn("SortName").AsString().Nullable()
.WithColumn("Description").AsString().Nullable()
.WithColumn("ForeignAuthorId").AsString().Nullable()
.WithColumn("Monitored").AsBoolean().WithDefaultValue(false)
.WithColumn("Path").AsString().Nullable()
.WithColumn("RootFolderPath").AsString().Nullable()
.WithColumn("QualityProfileId").AsInt32().WithDefaultValue(0)
.WithColumn("Added").AsDateTime().WithDefaultValue(System.DateTime.UtcNow)
.WithColumn("Tags").AsString().WithDefaultValue("[]");
Create.TableForModel("Series")
.WithColumn("Title").AsString().NotNullable()
.WithColumn("SortTitle").AsString().Nullable()
.WithColumn("Description").AsString().Nullable()
.WithColumn("ForeignSeriesId").AsString().Nullable()
.WithColumn("AuthorId").AsInt32().Nullable()
.WithColumn("Monitored").AsBoolean().WithDefaultValue(false);
Alter.Table("Movies")
.AddColumn("AuthorId").AsInt32().Nullable()
.AddColumn("SeriesId").AsInt32().Nullable();
}
}
}

View file

@ -4,6 +4,7 @@
using Dapper;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Authors;
using NzbDrone.Core.AutoTagging.Specifications;
using NzbDrone.Core.Blocklisting;
using NzbDrone.Core.Configuration;
@ -114,6 +115,10 @@ public static void Map()
Mapper.Entity<MovieFile>("MovieFiles").RegisterModel()
.Ignore(f => f.Path);
Mapper.Entity<Author>("Authors").RegisterModel();
Mapper.Entity<NzbDrone.Core.Series.Series>("Series").RegisterModel();
Mapper.Entity<Movie>("Movies").RegisterModel()
.Ignore(s => s.RootFolderPath)
.Ignore(s => s.Title)

View file

@ -21,6 +21,9 @@ protected MediaItem()
public HashSet<int> Tags { get; set; }
public DateTime? LastSearchTime { get; set; }
public int? AuthorId { get; set; }
public int? SeriesId { get; set; }
public abstract string GetTitle();
public abstract int GetYear();
}

View file

@ -0,0 +1,19 @@
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Series
{
public class Series : ModelBase
{
public string Title { get; set; }
public string SortTitle { get; set; }
public string Description { get; set; }
public string ForeignSeriesId { get; set; }
public int? AuthorId { get; set; }
public bool Monitored { get; set; }
public override string ToString()
{
return Title;
}
}
}