Migrate User DB to EF Core
This commit is contained in:
parent
aca7e221d8
commit
3eeb6576d8
|
@ -562,7 +562,6 @@ namespace Emby.Server.Implementations
|
|||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Class SQLiteDisplayPreferencesRepository.
|
||||
/// </summary>
|
||||
public class SqliteDisplayPreferencesRepository : BaseSqliteRepository, IDisplayPreferencesRepository
|
||||
{
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public SqliteDisplayPreferencesRepository(ILogger<SqliteDisplayPreferencesRepository> logger, IApplicationPaths appPaths, IFileSystem fileSystem)
|
||||
: base(logger)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
_jsonOptions = JsonDefaults.GetOptions();
|
||||
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "displaypreferences.db");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the repository.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name => "SQLite";
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeInternal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
||||
|
||||
_fileSystem.DeleteFile(DbFilePath);
|
||||
|
||||
InitializeInternal();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the connection to the database
|
||||
/// </summary>
|
||||
/// <returns>Task.</returns>
|
||||
private void InitializeInternal()
|
||||
{
|
||||
string[] queries =
|
||||
{
|
||||
"create table if not exists userdisplaypreferences (id GUID NOT NULL, userId GUID NOT NULL, client text NOT NULL, data BLOB NOT NULL)",
|
||||
"create unique index if not exists userdisplaypreferencesindex on userdisplaypreferences (id, userId, client)"
|
||||
};
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunQueries(queries);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the display preferences associated with an item in the repo
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(displayPreferences.Id))
|
||||
{
|
||||
throw new ArgumentException("Display preferences has an invalid Id", nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db => SaveDisplayPreferences(displayPreferences, userId, client, db),
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveDisplayPreferences(DisplayPreferences displayPreferences, Guid userId, string client, IDatabaseConnection connection)
|
||||
{
|
||||
var serialized = JsonSerializer.SerializeToUtf8Bytes(displayPreferences, _jsonOptions);
|
||||
|
||||
using (var statement = connection.PrepareStatement("replace into userdisplaypreferences (id, userid, client, data) values (@id, @userId, @client, @data)"))
|
||||
{
|
||||
statement.TryBind("@id", new Guid(displayPreferences.Id).ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
statement.TryBind("@data", serialized);
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save all display preferences associated with a user in the repo
|
||||
/// </summary>
|
||||
/// <param name="displayPreferences">The display preferences.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (displayPreferences == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferences));
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
using (var connection = GetConnection())
|
||||
{
|
||||
connection.RunInTransaction(
|
||||
db =>
|
||||
{
|
||||
foreach (var displayPreference in displayPreferences)
|
||||
{
|
||||
SaveDisplayPreferences(displayPreference, userId, displayPreference.Client, db);
|
||||
}
|
||||
},
|
||||
TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display preferences.
|
||||
/// </summary>
|
||||
/// <param name="displayPreferencesId">The display preferences id.</param>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="client">The client.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, Guid userId, string client)
|
||||
{
|
||||
if (string.IsNullOrEmpty(displayPreferencesId))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(displayPreferencesId));
|
||||
}
|
||||
|
||||
var guidId = displayPreferencesId.GetMD5();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
{
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where id = @id and userId=@userId and client=@client"))
|
||||
{
|
||||
statement.TryBind("@id", guidId.ToByteArray());
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
statement.TryBind("@client", client);
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
return Get(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new DisplayPreferences
|
||||
{
|
||||
Id = guidId.ToString("N", CultureInfo.InvariantCulture)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all display preferences for the given user.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <returns>Task{DisplayPreferences}.</returns>
|
||||
/// <exception cref="ArgumentNullException">item</exception>
|
||||
public IEnumerable<DisplayPreferences> GetAllDisplayPreferences(Guid userId)
|
||||
{
|
||||
var list = new List<DisplayPreferences>();
|
||||
|
||||
using (var connection = GetConnection(true))
|
||||
using (var statement = connection.PrepareStatement("select data from userdisplaypreferences where userId=@userId"))
|
||||
{
|
||||
statement.TryBind("@userId", userId.ToByteArray());
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
list.Add(Get(row));
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private DisplayPreferences Get(IReadOnlyList<IResultSetValue> row)
|
||||
=> JsonSerializer.Deserialize<DisplayPreferences>(row[0].ToBlob(), _jsonOptions);
|
||||
|
||||
public void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client, CancellationToken cancellationToken)
|
||||
=> SaveDisplayPreferences(displayPreferences, new Guid(userId), client, cancellationToken);
|
||||
|
||||
public DisplayPreferences GetDisplayPreferences(string displayPreferencesId, string userId, string client)
|
||||
=> GetDisplayPreferences(displayPreferencesId, new Guid(userId), client);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
|
@ -14,7 +15,6 @@ using MediaBrowser.Controller.Devices;
|
|||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
|
@ -27,6 +27,7 @@ using MediaBrowser.Model.Library;
|
|||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Session;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||
|
||||
namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
|
@ -254,7 +255,7 @@ namespace Emby.Server.Implementations.Session
|
|||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
Jellyfin.Data.Entities.User user)
|
||||
User user)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
|
@ -438,7 +439,7 @@ namespace Emby.Server.Implementations.Session
|
|||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
Jellyfin.Data.Entities.User user)
|
||||
User user)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
|
@ -457,7 +458,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
sessionInfo.UserId = user?.Id ?? Guid.Empty;
|
||||
sessionInfo.UserName = user?.Username;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
sessionInfo.Client = appName;
|
||||
|
||||
|
@ -483,7 +484,7 @@ namespace Emby.Server.Implementations.Session
|
|||
string deviceId,
|
||||
string deviceName,
|
||||
string remoteEndPoint,
|
||||
Jellyfin.Data.Entities.User user)
|
||||
User user)
|
||||
{
|
||||
var sessionInfo = new SessionInfo(this, _logger)
|
||||
{
|
||||
|
@ -497,7 +498,7 @@ namespace Emby.Server.Implementations.Session
|
|||
|
||||
sessionInfo.UserId = user?.Id ?? Guid.Empty;
|
||||
sessionInfo.UserName = username;
|
||||
sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.UserPrimaryImageTag = user?.ProfileImage == null ? null : GetImageCacheTag(user);
|
||||
sessionInfo.RemoteEndPoint = remoteEndPoint;
|
||||
|
||||
if (string.IsNullOrEmpty(deviceName))
|
||||
|
@ -520,9 +521,9 @@ namespace Emby.Server.Implementations.Session
|
|||
return sessionInfo;
|
||||
}
|
||||
|
||||
private List<Jellyfin.Data.Entities.User> GetUsers(SessionInfo session)
|
||||
private List<User> GetUsers(SessionInfo session)
|
||||
{
|
||||
var users = new List<Jellyfin.Data.Entities.User>();
|
||||
var users = new List<User>();
|
||||
|
||||
if (session.UserId != Guid.Empty)
|
||||
{
|
||||
|
@ -680,7 +681,7 @@ namespace Emby.Server.Implementations.Session
|
|||
/// </summary>
|
||||
/// <param name="user">The user object.</param>
|
||||
/// <param name="item">The item.</param>
|
||||
private void OnPlaybackStart(Jellyfin.Data.Entities.User user, BaseItem item)
|
||||
private void OnPlaybackStart(User user, BaseItem item)
|
||||
{
|
||||
var data = _userDataManager.GetUserData(user, item);
|
||||
|
||||
|
@ -763,7 +764,7 @@ namespace Emby.Server.Implementations.Session
|
|||
StartIdleCheckTimer();
|
||||
}
|
||||
|
||||
private void OnPlaybackProgress(Jellyfin.Data.Entities.User user, BaseItem item, PlaybackProgressInfo info)
|
||||
private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
|
||||
{
|
||||
var data = _userDataManager.GetUserData(user, item);
|
||||
|
||||
|
@ -789,7 +790,7 @@ namespace Emby.Server.Implementations.Session
|
|||
}
|
||||
}
|
||||
|
||||
private static bool UpdatePlaybackSettings(Jellyfin.Data.Entities.User user, PlaybackProgressInfo info, UserItemData data)
|
||||
private static bool UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
|
@ -949,7 +950,7 @@ namespace Emby.Server.Implementations.Session
|
|||
_logger);
|
||||
}
|
||||
|
||||
private bool OnPlaybackStopped(Jellyfin.Data.Entities.User user, BaseItem item, long? positionTicks, bool playbackFailed)
|
||||
private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed)
|
||||
{
|
||||
bool playedToCompletion = false;
|
||||
|
||||
|
@ -1163,7 +1164,7 @@ namespace Emby.Server.Implementations.Session
|
|||
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, Jellyfin.Data.Entities.User user)
|
||||
private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
|
@ -1216,7 +1217,7 @@ namespace Emby.Server.Implementations.Session
|
|||
return new[] { item };
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, Jellyfin.Data.Entities.User user)
|
||||
private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
|
@ -1399,7 +1400,7 @@ namespace Emby.Server.Implementations.Session
|
|||
{
|
||||
CheckDisposed();
|
||||
|
||||
Jellyfin.Data.Entities.User user = null;
|
||||
User user = null;
|
||||
if (request.UserId != Guid.Empty)
|
||||
{
|
||||
user = _userManager.GetUserById(request.UserId);
|
||||
|
@ -1455,7 +1456,7 @@ namespace Emby.Server.Implementations.Session
|
|||
return returnResult;
|
||||
}
|
||||
|
||||
private string GetAuthorizationToken(Jellyfin.Data.Entities.User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
private string GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
|
||||
{
|
||||
var existing = _authRepo.Get(
|
||||
new AuthenticationInfoQuery
|
||||
|
@ -1701,7 +1702,7 @@ namespace Emby.Server.Implementations.Session
|
|||
return info;
|
||||
}
|
||||
|
||||
private string GetImageCacheTag(Jellyfin.Data.Entities.User user)
|
||||
private string GetImageCacheTag(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace Jellyfin.Data.Entities
|
||||
|
@ -20,8 +22,9 @@ namespace Jellyfin.Data.Entities
|
|||
/// <param name="dayOfWeek">The day of the week.</param>
|
||||
/// <param name="startHour">The start hour.</param>
|
||||
/// <param name="endHour">The end hour.</param>
|
||||
public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour)
|
||||
public AccessSchedule(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
|
||||
{
|
||||
UserId = userId;
|
||||
DayOfWeek = dayOfWeek;
|
||||
StartHour = startHour;
|
||||
EndHour = endHour;
|
||||
|
@ -34,15 +37,20 @@ namespace Jellyfin.Data.Entities
|
|||
/// <param name="startHour">The start hour.</param>
|
||||
/// <param name="endHour">The end hour.</param>
|
||||
/// <returns>The newly created instance.</returns>
|
||||
public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour)
|
||||
public static AccessSchedule CreateInstance(DynamicDayOfWeek dayOfWeek, double startHour, double endHour, Guid userId)
|
||||
{
|
||||
return new AccessSchedule(dayOfWeek, startHour, endHour);
|
||||
return new AccessSchedule(dayOfWeek, startHour, endHour, userId);
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
[Key]
|
||||
[Required]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; protected set; }
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[ForeignKey("Id")]
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the day of week.
|
||||
|
|
|
@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||
|
||||
namespace Jellyfin.Data.Entities
|
||||
{
|
||||
public partial class Group
|
||||
public partial class Group : IHasPermissions, ISavingChanges
|
||||
{
|
||||
partial void Init();
|
||||
|
||||
|
@ -14,35 +14,29 @@ namespace Jellyfin.Data.Entities
|
|||
/// </summary>
|
||||
protected Group()
|
||||
{
|
||||
GroupPermissions = new HashSet<Permission>();
|
||||
Permissions = new HashSet<Permission>();
|
||||
ProviderMappings = new HashSet<ProviderMapping>();
|
||||
Preferences = new HashSet<Preference>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
|
||||
/// </summary>
|
||||
public static Group CreateGroupUnsafe()
|
||||
{
|
||||
return new Group();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor with required data
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="_user0"></param>
|
||||
public Group(string name, User _user0)
|
||||
/// <param name="user"></param>
|
||||
public Group(string name, User user)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
this.Name = name;
|
||||
user.Groups.Add(this);
|
||||
|
||||
if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
|
||||
_user0.Groups.Add(this);
|
||||
|
||||
this.GroupPermissions = new HashSet<Permission>();
|
||||
this.Permissions = new HashSet<Permission>();
|
||||
this.ProviderMappings = new HashSet<ProviderMapping>();
|
||||
this.Preferences = new HashSet<Preference>();
|
||||
|
||||
|
@ -54,9 +48,9 @@ namespace Jellyfin.Data.Entities
|
|||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="_user0"></param>
|
||||
public static Group Create(string name, User _user0)
|
||||
public static Group Create(string name, User user)
|
||||
{
|
||||
return new Group(name, _user0);
|
||||
return new Group(name, user);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -68,8 +62,7 @@ namespace Jellyfin.Data.Entities
|
|||
/// </summary>
|
||||
[Key]
|
||||
[Required]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; protected set; }
|
||||
public Guid Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required, Max length = 255
|
||||
|
@ -96,13 +89,13 @@ namespace Jellyfin.Data.Entities
|
|||
*************************************************************************/
|
||||
|
||||
[ForeignKey("Permission_GroupPermissions_Id")]
|
||||
public virtual ICollection<Permission> GroupPermissions { get; protected set; }
|
||||
public ICollection<Permission> Permissions { get; protected set; }
|
||||
|
||||
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
|
||||
public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
|
||||
public ICollection<ProviderMapping> ProviderMappings { get; protected set; }
|
||||
|
||||
[ForeignKey("Preference_Preferences_Id")]
|
||||
public virtual ICollection<Preference> Preferences { get; protected set; }
|
||||
public ICollection<Preference> Preferences { get; protected set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ using System.ComponentModel;
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace Jellyfin.Data.Entities
|
||||
{
|
||||
public partial class Permission
|
||||
public partial class Permission : ISavingChanges
|
||||
{
|
||||
partial void Init();
|
||||
|
||||
|
@ -18,33 +19,16 @@ namespace Jellyfin.Data.Entities
|
|||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
|
||||
/// </summary>
|
||||
public static Permission CreatePermissionUnsafe()
|
||||
{
|
||||
return new Permission();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor with required data
|
||||
/// </summary>
|
||||
/// <param name="kind"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="_user0"></param>
|
||||
/// <param name="_group1"></param>
|
||||
public Permission(Enums.PermissionKind kind, bool value, User _user0, Group _group1)
|
||||
/// <param name="holderId"></param>
|
||||
public Permission(PermissionKind kind, bool value)
|
||||
{
|
||||
this.Kind = kind;
|
||||
|
||||
this.Value = value;
|
||||
|
||||
if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
|
||||
_user0.Permissions.Add(this);
|
||||
|
||||
if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
|
||||
_group1.GroupPermissions.Add(this);
|
||||
|
||||
Kind = kind;
|
||||
Value = value;
|
||||
|
||||
Init();
|
||||
}
|
||||
|
@ -54,11 +38,10 @@ namespace Jellyfin.Data.Entities
|
|||
/// </summary>
|
||||
/// <param name="kind"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="_user0"></param>
|
||||
/// <param name="_group1"></param>
|
||||
public static Permission Create(Enums.PermissionKind kind, bool value, User _user0, Group _group1)
|
||||
/// <param name="holderId"></param>
|
||||
public static Permission Create(PermissionKind kind, bool value)
|
||||
{
|
||||
return new Permission(kind, value, _user0, _group1);
|
||||
return new Permission(kind, value);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -76,31 +59,32 @@ namespace Jellyfin.Data.Entities
|
|||
/// <summary>
|
||||
/// Backing field for Kind
|
||||
/// </summary>
|
||||
protected Enums.PermissionKind _Kind;
|
||||
protected PermissionKind _Kind;
|
||||
/// <summary>
|
||||
/// When provided in a partial class, allows value of Kind to be changed before setting.
|
||||
/// </summary>
|
||||
partial void SetKind(Enums.PermissionKind oldValue, ref Enums.PermissionKind newValue);
|
||||
partial void SetKind(PermissionKind oldValue, ref PermissionKind newValue);
|
||||
/// <summary>
|
||||
/// When provided in a partial class, allows value of Kind to be changed before returning.
|
||||
/// </summary>
|
||||
partial void GetKind(ref Enums.PermissionKind result);
|
||||
partial void GetKind(ref PermissionKind result);
|
||||
|
||||
/// <summary>
|
||||
/// Required
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Enums.PermissionKind Kind
|
||||
public PermissionKind Kind
|
||||
{
|
||||
get
|
||||
{
|
||||
Enums.PermissionKind value = _Kind;
|
||||
PermissionKind value = _Kind;
|
||||
GetKind(ref value);
|
||||
return (_Kind = value);
|
||||
return _Kind = value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
Enums.PermissionKind oldValue = _Kind;
|
||||
PermissionKind oldValue = _Kind;
|
||||
SetKind(oldValue, ref value);
|
||||
if (oldValue != value)
|
||||
{
|
||||
|
@ -117,7 +101,7 @@ namespace Jellyfin.Data.Entities
|
|||
public bool Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required, ConcurrenyToken
|
||||
/// Required, ConcurrencyToken.
|
||||
/// </summary>
|
||||
[ConcurrencyCheck]
|
||||
[Required]
|
||||
|
@ -138,7 +122,6 @@ namespace Jellyfin.Data.Entities
|
|||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,63 +1,33 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
namespace Jellyfin.Data.Entities
|
||||
{
|
||||
public partial class Preference
|
||||
/// <summary>
|
||||
/// An entity representing a preference attached to a user or group.
|
||||
/// </summary>
|
||||
public class Preference : ISavingChanges
|
||||
{
|
||||
partial void Init();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Preference"/> class.
|
||||
/// Public constructor with required data.
|
||||
/// </summary>
|
||||
/// <param name="kind">The preference kind.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public Preference(PreferenceKind kind, string value)
|
||||
{
|
||||
Kind = kind;
|
||||
Value = value ?? throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Preference"/> class.
|
||||
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
||||
/// </summary>
|
||||
protected Preference()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
|
||||
/// </summary>
|
||||
public static Preference CreatePreferenceUnsafe()
|
||||
{
|
||||
return new Preference();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor with required data
|
||||
/// </summary>
|
||||
/// <param name="kind"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="_user0"></param>
|
||||
/// <param name="_group1"></param>
|
||||
public Preference(Enums.PreferenceKind kind, string value, User _user0, Group _group1)
|
||||
{
|
||||
this.Kind = kind;
|
||||
|
||||
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));
|
||||
this.Value = value;
|
||||
|
||||
if (_user0 == null) throw new ArgumentNullException(nameof(_user0));
|
||||
_user0.Preferences.Add(this);
|
||||
|
||||
if (_group1 == null) throw new ArgumentNullException(nameof(_group1));
|
||||
_group1.Preferences.Add(this);
|
||||
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static create function (for use in LINQ queries, etc.)
|
||||
/// </summary>
|
||||
/// <param name="kind"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="_user0"></param>
|
||||
/// <param name="_group1"></param>
|
||||
public static Preference Create(Enums.PreferenceKind kind, string value, User _user0, Group _group1)
|
||||
{
|
||||
return new Preference(kind, value, _user0, _group1);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -76,7 +46,7 @@ namespace Jellyfin.Data.Entities
|
|||
/// Required
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Enums.PreferenceKind Kind { get; set; }
|
||||
public PreferenceKind Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required, Max length = 65535
|
||||
|
@ -87,21 +57,28 @@ namespace Jellyfin.Data.Entities
|
|||
public string Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required, ConcurrenyToken
|
||||
/// Required, ConcurrencyToken.
|
||||
/// </summary>
|
||||
[ConcurrencyCheck]
|
||||
[Required]
|
||||
public uint RowVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Static create function (for use in LINQ queries, etc.)
|
||||
/// </summary>
|
||||
/// <param name="kind">The preference kind.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
/// <returns>The new instance.</returns>
|
||||
public static Preference Create(PreferenceKind kind, string value)
|
||||
{
|
||||
return new Preference(kind, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void OnSavingChanges()
|
||||
{
|
||||
RowVersion++;
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
* Navigation properties
|
||||
*************************************************************************/
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,45 +9,23 @@ using Jellyfin.Data.Enums;
|
|||
|
||||
namespace Jellyfin.Data.Entities
|
||||
{
|
||||
public partial class User
|
||||
/// <summary>
|
||||
/// An entity representing a user.
|
||||
/// </summary>
|
||||
public partial class User : IHasPermissions, ISavingChanges
|
||||
{
|
||||
/// <summary>
|
||||
/// The values being delimited here are Guids, so commas work as they do not appear in Guids.
|
||||
/// </summary>
|
||||
private const char Delimiter = ',';
|
||||
|
||||
partial void Init();
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
||||
/// Initializes a new instance of the <see cref="User"/> class.
|
||||
/// Public constructor with required data.
|
||||
/// </summary>
|
||||
protected User()
|
||||
{
|
||||
Groups = new HashSet<Group>();
|
||||
Permissions = new HashSet<Permission>();
|
||||
ProviderMappings = new HashSet<ProviderMapping>();
|
||||
Preferences = new HashSet<Preference>();
|
||||
AccessSchedules = new HashSet<AccessSchedule>();
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Public constructor with required data
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="mustUpdatePassword"></param>
|
||||
/// <param name="authenticationProviderId"></param>
|
||||
/// <param name="invalidLoginAttemptCount"></param>
|
||||
/// <param name="subtitleMode"></param>
|
||||
/// <param name="playDefaultAudioTrack"></param>
|
||||
public User(
|
||||
string username,
|
||||
bool mustUpdatePassword,
|
||||
string authenticationProviderId,
|
||||
int invalidLoginAttemptCount,
|
||||
SubtitlePlaybackMode subtitleMode,
|
||||
bool playDefaultAudioTrack)
|
||||
/// <param name="username">The username for the new user.</param>
|
||||
/// <param name="authenticationProviderId">The authentication provider's Id</param>
|
||||
public User(string username, string authenticationProviderId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(username))
|
||||
{
|
||||
|
@ -60,11 +38,7 @@ namespace Jellyfin.Data.Entities
|
|||
}
|
||||
|
||||
Username = username;
|
||||
MustUpdatePassword = mustUpdatePassword;
|
||||
AuthenticationProviderId = authenticationProviderId;
|
||||
InvalidLoginAttemptCount = invalidLoginAttemptCount;
|
||||
SubtitleMode = subtitleMode;
|
||||
PlayDefaultAudioTrack = playDefaultAudioTrack;
|
||||
|
||||
Groups = new HashSet<Group>();
|
||||
Permissions = new HashSet<Permission>();
|
||||
|
@ -74,6 +48,8 @@ namespace Jellyfin.Data.Entities
|
|||
|
||||
// Set default values
|
||||
Id = Guid.NewGuid();
|
||||
InvalidLoginAttemptCount = 0;
|
||||
MustUpdatePassword = false;
|
||||
DisplayMissingEpisodes = false;
|
||||
DisplayCollectionsView = false;
|
||||
HidePlayedInLatest = true;
|
||||
|
@ -81,36 +57,40 @@ namespace Jellyfin.Data.Entities
|
|||
RememberSubtitleSelections = true;
|
||||
EnableNextEpisodeAutoPlay = true;
|
||||
EnableAutoLogin = false;
|
||||
PlayDefaultAudioTrack = true;
|
||||
SubtitleMode = SubtitlePlaybackMode.Default;
|
||||
|
||||
AddDefaultPermissions();
|
||||
AddDefaultPreferences();
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
|
||||
/// Initializes a new instance of the <see cref="User"/> class.
|
||||
/// Default constructor. Protected due to required properties, but present because EF needs it.
|
||||
/// </summary>
|
||||
public static User CreateUserUnsafe()
|
||||
protected User()
|
||||
{
|
||||
return new User();
|
||||
Groups = new HashSet<Group>();
|
||||
Permissions = new HashSet<Permission>();
|
||||
ProviderMappings = new HashSet<ProviderMapping>();
|
||||
Preferences = new HashSet<Preference>();
|
||||
AccessSchedules = new HashSet<AccessSchedule>();
|
||||
|
||||
AddDefaultPermissions();
|
||||
AddDefaultPreferences();
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static create function (for use in LINQ queries, etc.)
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="mustUpdatePassword"></param>
|
||||
/// <param name="authenticationProviderId"></param>
|
||||
/// <param name="invalidLoginAttemptCount"></param>
|
||||
/// <param name="subtitleMode"></param>
|
||||
/// <param name="playDefaultAudioTrack"></param>
|
||||
public static User Create(
|
||||
string username,
|
||||
bool mustUpdatePassword,
|
||||
string authenticationProviderId,
|
||||
int invalidLoginAttemptCount,
|
||||
SubtitlePlaybackMode subtitleMode,
|
||||
bool playDefaultAudioTrack)
|
||||
/// <param name="username">The username for the created user.</param>
|
||||
/// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
|
||||
/// <returns>The created instance.</returns>
|
||||
public static User Create(string username, string authenticationProviderId)
|
||||
{
|
||||
return new User(username, mustUpdatePassword, authenticationProviderId, invalidLoginAttemptCount, subtitleMode, playDefaultAudioTrack);
|
||||
return new User(username, authenticationProviderId);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
|
@ -131,7 +111,6 @@ namespace Jellyfin.Data.Entities
|
|||
[Required]
|
||||
[MaxLength(255)]
|
||||
[StringLength(255)]
|
||||
[JsonPropertyName("Name")]
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -199,6 +178,7 @@ namespace Jellyfin.Data.Entities
|
|||
public bool PlayDefaultAudioTrack { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the subtitle language preference.
|
||||
/// Max length = 255
|
||||
/// </summary>
|
||||
[MaxLength(255)]
|
||||
|
@ -237,6 +217,7 @@ namespace Jellyfin.Data.Entities
|
|||
public int? RemoteClientBitrateLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the internal id.
|
||||
/// This is a temporary stopgap for until the library db is migrated.
|
||||
/// This corresponds to the value of the index of this user in the library db.
|
||||
/// </summary>
|
||||
|
@ -246,7 +227,8 @@ namespace Jellyfin.Data.Entities
|
|||
public ImageInfo ProfileImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Required, ConcurrenyToken
|
||||
/// Gets or sets the row version.
|
||||
/// Required, ConcurrenyToken.
|
||||
/// </summary>
|
||||
[ConcurrencyCheck]
|
||||
[Required]
|
||||
|
@ -260,23 +242,25 @@ namespace Jellyfin.Data.Entities
|
|||
/*************************************************************************
|
||||
* Navigation properties
|
||||
*************************************************************************/
|
||||
[ForeignKey("Group_Groups_Id")]
|
||||
[ForeignKey("Group_Groups_Guid")]
|
||||
public ICollection<Group> Groups { get; protected set; }
|
||||
|
||||
[ForeignKey("Permission_Permissions_Id")]
|
||||
[ForeignKey("Permission_Permissions_Guid")]
|
||||
public ICollection<Permission> Permissions { get; protected set; }
|
||||
|
||||
[ForeignKey("ProviderMapping_ProviderMappings_Id")]
|
||||
public ICollection<ProviderMapping> ProviderMappings { get; protected set; }
|
||||
|
||||
[ForeignKey("Preference_Preferences_Id")]
|
||||
[ForeignKey("Preference_Preferences_Guid")]
|
||||
public ICollection<Preference> Preferences { get; protected set; }
|
||||
|
||||
public ICollection<AccessSchedule> AccessSchedules { get; protected set; }
|
||||
|
||||
partial void Init();
|
||||
|
||||
public bool HasPermission(PermissionKind permission)
|
||||
{
|
||||
return Permissions.Select(p => p.Kind).Contains(permission);
|
||||
return Permissions.First(p => p.Kind == permission).Value;
|
||||
}
|
||||
|
||||
public void SetPermission(PermissionKind kind, bool value)
|
||||
|
@ -287,11 +271,12 @@ namespace Jellyfin.Data.Entities
|
|||
|
||||
public string[] GetPreference(PreferenceKind preference)
|
||||
{
|
||||
return Preferences
|
||||
var val = Preferences
|
||||
.Where(p => p.Kind == preference)
|
||||
.Select(p => p.Value)
|
||||
.First()
|
||||
.Split(Delimiter);
|
||||
.First();
|
||||
|
||||
return Equals(val, string.Empty) ? Array.Empty<string>() : val.Split(Delimiter);
|
||||
}
|
||||
|
||||
public void SetPreference(PreferenceKind preference, string[] values)
|
||||
|
@ -332,5 +317,39 @@ namespace Jellyfin.Data.Entities
|
|||
|
||||
return hour >= schedule.StartHour && hour <= schedule.EndHour;
|
||||
}
|
||||
|
||||
// TODO: make these user configurable?
|
||||
private void AddDefaultPermissions()
|
||||
{
|
||||
Permissions.Add(new Permission(PermissionKind.IsAdministrator, false));
|
||||
Permissions.Add(new Permission(PermissionKind.IsDisabled, false));
|
||||
Permissions.Add(new Permission(PermissionKind.IsHidden, false));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableAllChannels, false));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableAllDevices, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableAllFolders, false));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableContentDeletion, false));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableContentDownloading, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableMediaConversion, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableMediaPlayback, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnablePlaybackRemuxing, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnablePublicSharing, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableRemoteAccess, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true));
|
||||
Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false));
|
||||
Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false));
|
||||
}
|
||||
|
||||
private void AddDefaultPreferences()
|
||||
{
|
||||
foreach (var val in Enum.GetValues(typeof(PreferenceKind)).Cast<PreferenceKind>())
|
||||
{
|
||||
Preferences.Add(new Preference(val, string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
Jellyfin.Data/IHasPermissions.cs
Normal file
10
Jellyfin.Data/IHasPermissions.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace Jellyfin.Data
|
||||
{
|
||||
public interface IHasPermissions
|
||||
{
|
||||
ICollection<Permission> Permissions { get; }
|
||||
}
|
||||
}
|
|
@ -16,6 +16,13 @@ namespace Jellyfin.Server.Implementations
|
|||
public partial class JellyfinDb : DbContext
|
||||
{
|
||||
public virtual DbSet<ActivityLog> ActivityLogs { get; set; }
|
||||
|
||||
public virtual DbSet<Group> Groups { get; set; }
|
||||
|
||||
public virtual DbSet<Permission> Permissions { get; set; }
|
||||
|
||||
public virtual DbSet<Preference> Preferences { get; set; }
|
||||
|
||||
public virtual DbSet<Data.Entities.User> Users { get; set; }
|
||||
/*public virtual DbSet<Artwork> Artwork { get; set; }
|
||||
public virtual DbSet<Book> Books { get; set; }
|
||||
|
@ -30,7 +37,6 @@ namespace Jellyfin.Server.Implementations
|
|||
public virtual DbSet<Episode> Episodes { get; set; }
|
||||
public virtual DbSet<EpisodeMetadata> EpisodeMetadata { get; set; }
|
||||
public virtual DbSet<Genre> Genres { get; set; }
|
||||
public virtual DbSet<Group> Groups { get; set; }
|
||||
public virtual DbSet<Library> Libraries { get; set; }
|
||||
public virtual DbSet<LibraryItem> LibraryItems { get; set; }
|
||||
public virtual DbSet<LibraryRoot> LibraryRoot { get; set; }
|
||||
|
@ -43,12 +49,10 @@ namespace Jellyfin.Server.Implementations
|
|||
public virtual DbSet<MovieMetadata> MovieMetadata { get; set; }
|
||||
public virtual DbSet<MusicAlbum> MusicAlbums { get; set; }
|
||||
public virtual DbSet<MusicAlbumMetadata> MusicAlbumMetadata { get; set; }
|
||||
public virtual DbSet<Permission> Permissions { get; set; }
|
||||
public virtual DbSet<Person> People { get; set; }
|
||||
public virtual DbSet<PersonRole> PersonRoles { get; set; }
|
||||
public virtual DbSet<Photo> Photo { get; set; }
|
||||
public virtual DbSet<PhotoMetadata> PhotoMetadata { get; set; }
|
||||
public virtual DbSet<Preference> Preferences { get; set; }
|
||||
public virtual DbSet<ProviderMapping> ProviderMappings { get; set; }
|
||||
public virtual DbSet<Rating> Ratings { get; set; }
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace Jellyfin.Server.Implementations
|
|||
public JellyfinDbProvider(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
serviceProvider.GetService<JellyfinDb>().Database.Migrate();
|
||||
serviceProvider.GetRequiredService<JellyfinDb>().Database.Migrate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1601
|
||||
|
||||
// <auto-generated />
|
||||
|
@ -12,8 +12,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
[DbContext(typeof(JellyfinDb))]
|
||||
[Migration("20200504195702_UserSchema")]
|
||||
partial class UserSchema
|
||||
[Migration("20200517002411_AddUsers")]
|
||||
partial class AddUsers
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
|
@ -22,6 +22,31 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasDefaultSchema("jellyfin")
|
||||
.HasAnnotation("ProductVersion", "3.1.3");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DayOfWeek")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("EndHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("StartHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AccessSchedule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -65,17 +90,17 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ActivityLog");
|
||||
b.ToTable("ActivityLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Group_Groups_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Group_Groups_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
|
@ -88,9 +113,27 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Group_Groups_Id");
|
||||
b.HasIndex("Group_Groups_Guid");
|
||||
|
||||
b.ToTable("Group");
|
||||
b.ToTable("Groups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ImageInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
|
@ -102,11 +145,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Permission_GroupPermissions_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Permission_GroupPermissions_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Permission_Permissions_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
|
@ -119,9 +162,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasIndex("Permission_GroupPermissions_Id");
|
||||
|
||||
b.HasIndex("Permission_Permissions_Id");
|
||||
b.HasIndex("Permission_Permissions_Guid");
|
||||
|
||||
b.ToTable("Permission");
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
|
@ -133,8 +176,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Preference_Preferences_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("Preference_Preferences_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
|
@ -147,9 +193,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Preference_Preferences_Guid");
|
||||
|
||||
b.HasIndex("Preference_Preferences_Id");
|
||||
|
||||
b.ToTable("Preference");
|
||||
b.ToTable("Preferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
|
||||
|
@ -163,8 +211,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<int?>("ProviderMapping_ProviderMappings_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("ProviderMapping_ProviderMappings_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.IsRequired()
|
||||
|
@ -189,12 +237,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AudioLanguagePreference")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
|
@ -203,71 +250,86 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<bool?>("DisplayCollectionsView")
|
||||
b.Property<bool>("DisplayCollectionsView")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("DisplayMissingEpisodes")
|
||||
b.Property<bool>("DisplayMissingEpisodes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("EnableNextEpisodeAutoPlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("EnableUserPreferenceAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("GroupedFolders")
|
||||
b.Property<string>("EasyPassword")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<bool?>("HidePlayedInLatest")
|
||||
b.Property<bool>("EnableAutoLogin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableLocalPassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableUserPreferenceAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HidePlayedInLatest")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("InternalId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("InvalidLoginAttemptCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LatestItemExcludes")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
b.Property<DateTime>("LastActivityDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastLoginDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MaxParentalAgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("MustUpdatePassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MyMediaExcludes")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("OrderedViews")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("PasswordResetProviderId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<bool>("PlayDefaultAudioTrack")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("RememberAudioSelections")
|
||||
b.Property<int?>("ProfileImageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("RememberSubtitleSelections")
|
||||
b.Property<bool>("RememberAudioSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("RememberSubtitleSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RemoteClientBitrateLimit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SubtitleLanguagePrefernce")
|
||||
b.Property<string>("SubtitleLanguagePreference")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<string>("SubtitleMode")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
b.Property<int>("SubtitleMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
|
@ -276,34 +338,45 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("User");
|
||||
b.HasIndex("ProfileImageId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("AccessSchedules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Groups")
|
||||
.HasForeignKey("Group_Groups_Id");
|
||||
.HasForeignKey("Group_Groups_Guid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("GroupPermissions")
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("Permission_GroupPermissions_Id");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("Permission_Permissions_Id");
|
||||
.HasForeignKey("Permission_Permissions_Guid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Id");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Guid");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Id");
|
||||
});
|
||||
|
@ -318,6 +391,13 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.WithMany("ProviderMappings")
|
||||
.HasForeignKey("ProviderMapping_ProviderMappings_Id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProfileImageId");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
|
@ -1,74 +1,125 @@
|
|||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable SA1601
|
||||
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
public partial class UserSchema : Migration
|
||||
public partial class AddUsers : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "User",
|
||||
name: "ImageInfo",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Path = table.Column<string>(nullable: false),
|
||||
LastModified = table.Column<DateTime>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ImageInfo", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(nullable: false),
|
||||
Username = table.Column<string>(maxLength: 255, nullable: false),
|
||||
Password = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
EasyPassword = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
MustUpdatePassword = table.Column<bool>(nullable: false),
|
||||
AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: false),
|
||||
AudioLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
|
||||
AuthenticationProviderId = table.Column<string>(maxLength: 255, nullable: false),
|
||||
GroupedFolders = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
PasswordResetProviderId = table.Column<string>(maxLength: 255, nullable: false),
|
||||
InvalidLoginAttemptCount = table.Column<int>(nullable: false),
|
||||
LatestItemExcludes = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
LastActivityDate = table.Column<DateTime>(nullable: false),
|
||||
LastLoginDate = table.Column<DateTime>(nullable: false),
|
||||
LoginAttemptsBeforeLockout = table.Column<int>(nullable: true),
|
||||
MyMediaExcludes = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
OrderedViews = table.Column<string>(maxLength: 65535, nullable: true),
|
||||
SubtitleMode = table.Column<string>(maxLength: 255, nullable: false),
|
||||
SubtitleMode = table.Column<int>(nullable: false),
|
||||
PlayDefaultAudioTrack = table.Column<bool>(nullable: false),
|
||||
SubtitleLanguagePrefernce = table.Column<string>(maxLength: 255, nullable: true),
|
||||
DisplayMissingEpisodes = table.Column<bool>(nullable: true),
|
||||
DisplayCollectionsView = table.Column<bool>(nullable: true),
|
||||
HidePlayedInLatest = table.Column<bool>(nullable: true),
|
||||
RememberAudioSelections = table.Column<bool>(nullable: true),
|
||||
RememberSubtitleSelections = table.Column<bool>(nullable: true),
|
||||
EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: true),
|
||||
EnableUserPreferenceAccess = table.Column<bool>(nullable: true),
|
||||
SubtitleLanguagePreference = table.Column<string>(maxLength: 255, nullable: true),
|
||||
DisplayMissingEpisodes = table.Column<bool>(nullable: false),
|
||||
DisplayCollectionsView = table.Column<bool>(nullable: false),
|
||||
EnableLocalPassword = table.Column<bool>(nullable: false),
|
||||
HidePlayedInLatest = table.Column<bool>(nullable: false),
|
||||
RememberAudioSelections = table.Column<bool>(nullable: false),
|
||||
RememberSubtitleSelections = table.Column<bool>(nullable: false),
|
||||
EnableNextEpisodeAutoPlay = table.Column<bool>(nullable: false),
|
||||
EnableAutoLogin = table.Column<bool>(nullable: false),
|
||||
EnableUserPreferenceAccess = table.Column<bool>(nullable: false),
|
||||
MaxParentalAgeRating = table.Column<int>(nullable: true),
|
||||
RemoteClientBitrateLimit = table.Column<int>(nullable: true),
|
||||
InternalId = table.Column<long>(nullable: false),
|
||||
ProfileImageId = table.Column<int>(nullable: true),
|
||||
RowVersion = table.Column<uint>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_User", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Group",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(maxLength: 255, nullable: false),
|
||||
RowVersion = table.Column<uint>(nullable: false),
|
||||
Group_Groups_Id = table.Column<int>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Group", x => x.Id);
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Group_User_Group_Groups_Id",
|
||||
column: x => x.Group_Groups_Id,
|
||||
name: "FK_Users_ImageInfo_ProfileImageId",
|
||||
column: x => x.ProfileImageId,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "User",
|
||||
principalTable: "ImageInfo",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permission",
|
||||
name: "AccessSchedule",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<Guid>(nullable: false),
|
||||
DayOfWeek = table.Column<int>(nullable: false),
|
||||
StartHour = table.Column<double>(nullable: false),
|
||||
EndHour = table.Column<double>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AccessSchedule", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AccessSchedule_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Groups",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(nullable: false),
|
||||
Name = table.Column<string>(maxLength: 255, nullable: false),
|
||||
RowVersion = table.Column<uint>(nullable: false),
|
||||
Group_Groups_Guid = table.Column<Guid>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Groups", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Groups_Users_Group_Groups_Guid",
|
||||
column: x => x.Group_Groups_Guid,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permissions",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
|
@ -77,30 +128,30 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
Kind = table.Column<int>(nullable: false),
|
||||
Value = table.Column<bool>(nullable: false),
|
||||
RowVersion = table.Column<uint>(nullable: false),
|
||||
Permission_GroupPermissions_Id = table.Column<int>(nullable: true),
|
||||
Permission_Permissions_Id = table.Column<int>(nullable: true)
|
||||
Permission_GroupPermissions_Id = table.Column<Guid>(nullable: true),
|
||||
Permission_Permissions_Guid = table.Column<Guid>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Permission", x => x.Id);
|
||||
table.PrimaryKey("PK_Permissions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Permission_Group_Permission_GroupPermissions_Id",
|
||||
name: "FK_Permissions_Groups_Permission_GroupPermissions_Id",
|
||||
column: x => x.Permission_GroupPermissions_Id,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Group",
|
||||
principalTable: "Groups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Permission_User_Permission_Permissions_Id",
|
||||
column: x => x.Permission_Permissions_Id,
|
||||
name: "FK_Permissions_Users_Permission_Permissions_Guid",
|
||||
column: x => x.Permission_Permissions_Guid,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "User",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Preference",
|
||||
name: "Preferences",
|
||||
schema: "jellyfin",
|
||||
columns: table => new
|
||||
{
|
||||
|
@ -109,23 +160,24 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
Kind = table.Column<int>(nullable: false),
|
||||
Value = table.Column<string>(maxLength: 65535, nullable: false),
|
||||
RowVersion = table.Column<uint>(nullable: false),
|
||||
Preference_Preferences_Id = table.Column<int>(nullable: true)
|
||||
Preference_Preferences_Guid = table.Column<Guid>(nullable: true),
|
||||
Preference_Preferences_Id = table.Column<Guid>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Preference", x => x.Id);
|
||||
table.PrimaryKey("PK_Preferences", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Preference_Group_Preference_Preferences_Id",
|
||||
column: x => x.Preference_Preferences_Id,
|
||||
name: "FK_Preferences_Users_Preference_Preferences_Guid",
|
||||
column: x => x.Preference_Preferences_Guid,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Group",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Preference_User_Preference_Preferences_Id",
|
||||
name: "FK_Preferences_Groups_Preference_Preferences_Id",
|
||||
column: x => x.Preference_Preferences_Id,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "User",
|
||||
principalTable: "Groups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
@ -141,49 +193,61 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
ProviderSecrets = table.Column<string>(maxLength: 65535, nullable: false),
|
||||
ProviderData = table.Column<string>(maxLength: 65535, nullable: false),
|
||||
RowVersion = table.Column<uint>(nullable: false),
|
||||
ProviderMapping_ProviderMappings_Id = table.Column<int>(nullable: true)
|
||||
ProviderMapping_ProviderMappings_Id = table.Column<Guid>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ProviderMapping", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ProviderMapping_Group_ProviderMapping_ProviderMappings_Id",
|
||||
name: "FK_ProviderMapping_Groups_ProviderMapping_ProviderMappings_Id",
|
||||
column: x => x.ProviderMapping_ProviderMappings_Id,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "Group",
|
||||
principalTable: "Groups",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_ProviderMapping_User_ProviderMapping_ProviderMappings_Id",
|
||||
name: "FK_ProviderMapping_Users_ProviderMapping_ProviderMappings_Id",
|
||||
column: x => x.ProviderMapping_ProviderMappings_Id,
|
||||
principalSchema: "jellyfin",
|
||||
principalTable: "User",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Group_Group_Groups_Id",
|
||||
name: "IX_AccessSchedule_UserId",
|
||||
schema: "jellyfin",
|
||||
table: "Group",
|
||||
column: "Group_Groups_Id");
|
||||
table: "AccessSchedule",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Permission_Permission_GroupPermissions_Id",
|
||||
name: "IX_Groups_Group_Groups_Guid",
|
||||
schema: "jellyfin",
|
||||
table: "Permission",
|
||||
table: "Groups",
|
||||
column: "Group_Groups_Guid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Permissions_Permission_GroupPermissions_Id",
|
||||
schema: "jellyfin",
|
||||
table: "Permissions",
|
||||
column: "Permission_GroupPermissions_Id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Permission_Permission_Permissions_Id",
|
||||
name: "IX_Permissions_Permission_Permissions_Guid",
|
||||
schema: "jellyfin",
|
||||
table: "Permission",
|
||||
column: "Permission_Permissions_Id");
|
||||
table: "Permissions",
|
||||
column: "Permission_Permissions_Guid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Preference_Preference_Preferences_Id",
|
||||
name: "IX_Preferences_Preference_Preferences_Guid",
|
||||
schema: "jellyfin",
|
||||
table: "Preference",
|
||||
table: "Preferences",
|
||||
column: "Preference_Preferences_Guid");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Preferences_Preference_Preferences_Id",
|
||||
schema: "jellyfin",
|
||||
table: "Preferences",
|
||||
column: "Preference_Preferences_Id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
|
@ -191,16 +255,26 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
schema: "jellyfin",
|
||||
table: "ProviderMapping",
|
||||
column: "ProviderMapping_ProviderMappings_Id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_ProfileImageId",
|
||||
schema: "jellyfin",
|
||||
table: "Users",
|
||||
column: "ProfileImageId");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Permission",
|
||||
name: "AccessSchedule",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Preference",
|
||||
name: "Permissions",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Preferences",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
|
@ -208,11 +282,15 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Group",
|
||||
name: "Groups",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "User",
|
||||
name: "Users",
|
||||
schema: "jellyfin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ImageInfo",
|
||||
schema: "jellyfin");
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
|
@ -15,6 +17,31 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasDefaultSchema("jellyfin")
|
||||
.HasAnnotation("ProductVersion", "3.1.3");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("DayOfWeek")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("EndHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("StartHour")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AccessSchedule");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -63,12 +90,12 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Group_Groups_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Group_Groups_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
|
@ -81,9 +108,27 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Group_Groups_Id");
|
||||
b.HasIndex("Group_Groups_Guid");
|
||||
|
||||
b.ToTable("Group");
|
||||
b.ToTable("Groups");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ImageInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
|
@ -95,11 +140,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Permission_GroupPermissions_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Permission_GroupPermissions_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Permission_Permissions_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
|
@ -112,9 +157,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasIndex("Permission_GroupPermissions_Id");
|
||||
|
||||
b.HasIndex("Permission_Permissions_Id");
|
||||
b.HasIndex("Permission_Permissions_Guid");
|
||||
|
||||
b.ToTable("Permission");
|
||||
b.ToTable("Permissions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
|
@ -126,8 +171,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
b.Property<int>("Kind")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("Preference_Preferences_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid?>("Preference_Preferences_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
|
@ -140,9 +188,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Preference_Preferences_Guid");
|
||||
|
||||
b.HasIndex("Preference_Preferences_Id");
|
||||
|
||||
b.ToTable("Preference");
|
||||
b.ToTable("Preferences");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.ProviderMapping", b =>
|
||||
|
@ -156,8 +206,8 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<int?>("ProviderMapping_ProviderMappings_Id")
|
||||
.HasColumnType("INTEGER");
|
||||
b.Property<Guid?>("ProviderMapping_ProviderMappings_Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderName")
|
||||
.IsRequired()
|
||||
|
@ -182,12 +232,11 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("AudioLanguagePreference")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
|
@ -196,71 +245,86 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<bool?>("DisplayCollectionsView")
|
||||
b.Property<bool>("DisplayCollectionsView")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("DisplayMissingEpisodes")
|
||||
b.Property<bool>("DisplayMissingEpisodes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("EnableNextEpisodeAutoPlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("EnableUserPreferenceAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("GroupedFolders")
|
||||
b.Property<string>("EasyPassword")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<bool?>("HidePlayedInLatest")
|
||||
b.Property<bool>("EnableAutoLogin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableLocalPassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("EnableUserPreferenceAccess")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HidePlayedInLatest")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("InternalId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("InvalidLoginAttemptCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("LatestItemExcludes")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
b.Property<DateTime>("LastActivityDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastLoginDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("MaxParentalAgeRating")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("MustUpdatePassword")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("MyMediaExcludes")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("OrderedViews")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(65535);
|
||||
|
||||
b.Property<string>("PasswordResetProviderId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<bool>("PlayDefaultAudioTrack")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("RememberAudioSelections")
|
||||
b.Property<int?>("ProfileImageId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool?>("RememberSubtitleSelections")
|
||||
b.Property<bool>("RememberAudioSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("RememberSubtitleSelections")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RemoteClientBitrateLimit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SubtitleLanguagePrefernce")
|
||||
b.Property<string>("SubtitleLanguagePreference")
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
|
||||
b.Property<string>("SubtitleMode")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT")
|
||||
.HasMaxLength(255);
|
||||
b.Property<int>("SubtitleMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
|
@ -269,34 +333,45 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("User");
|
||||
b.HasIndex("ProfileImageId");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("AccessSchedules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Group", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Groups")
|
||||
.HasForeignKey("Group_Groups_Id");
|
||||
.HasForeignKey("Group_Groups_Guid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("GroupPermissions")
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("Permission_GroupPermissions_Id");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Permissions")
|
||||
.HasForeignKey("Permission_Permissions_Id");
|
||||
.HasForeignKey("Permission_Permissions_Guid");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Id");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Guid");
|
||||
|
||||
b.HasOne("Jellyfin.Data.Entities.Group", null)
|
||||
.WithMany("Preferences")
|
||||
.HasForeignKey("Preference_Preferences_Id");
|
||||
});
|
||||
|
@ -311,6 +386,13 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||
.WithMany("ProviderMappings")
|
||||
.HasForeignKey("ProviderMapping_ProviderMappings_Id");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||
{
|
||||
b.HasOne("Jellyfin.Data.Entities.ImageInfo", "ProfileImage")
|
||||
.WithMany()
|
||||
.HasForeignKey("ProfileImageId");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ using MediaBrowser.Common.Cryptography;
|
|||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.User
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// The default authentication provider.
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
|
@ -10,7 +11,7 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.User
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// The default password reset provider.
|
||||
|
@ -94,7 +95,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(Jellyfin.Data.Entities.User user, bool isInNetwork)
|
||||
public async Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork)
|
||||
{
|
||||
string pin;
|
||||
using (var cryptoRandom = RandomNumberGenerator.Create())
|
|
@ -1,6 +1,7 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -9,7 +10,7 @@ using MediaBrowser.Controller.Security;
|
|||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Events;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.User
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
public sealed class DeviceAccessEntryPoint : IServerEntryPoint
|
||||
{
|
||||
|
@ -33,7 +34,11 @@ namespace Jellyfin.Server.Implementations.User
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OnUserUpdated(object sender, GenericEventArgs<Data.Entities.User> e)
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnUserUpdated(object sender, GenericEventArgs<User> e)
|
||||
{
|
||||
var user = e.Argument;
|
||||
if (!user.HasPermission(PermissionKind.EnableAllDevices))
|
||||
|
@ -42,11 +47,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private void UpdateDeviceAccess(Data.Entities.User user)
|
||||
private void UpdateDeviceAccess(User user)
|
||||
{
|
||||
var existing = _authRepo.Get(new AuthenticationInfoQuery
|
||||
{
|
|
@ -1,7 +1,7 @@
|
|||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.User
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
/// <summary>
|
||||
/// An invalid authentication provider.
|
|
@ -1,4 +1,4 @@
|
|||
#pragma warning disable CS0067
|
||||
#pragma warning disable CA1307
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
@ -6,7 +6,9 @@ using System.Collections.Generic;
|
|||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -20,7 +22,7 @@ using MediaBrowser.Model.Events;
|
|||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.User
|
||||
namespace Jellyfin.Server.Implementations.Users
|
||||
{
|
||||
public class UserManager : IUserManager
|
||||
{
|
||||
|
@ -47,24 +49,24 @@ namespace Jellyfin.Server.Implementations.User
|
|||
_logger = logger;
|
||||
}
|
||||
|
||||
public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserPasswordChanged;
|
||||
public event EventHandler<GenericEventArgs<User>> OnUserPasswordChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserUpdated;
|
||||
public event EventHandler<GenericEventArgs<User>> OnUserUpdated;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserCreated;
|
||||
public event EventHandler<GenericEventArgs<User>> OnUserCreated;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserDeleted;
|
||||
public event EventHandler<GenericEventArgs<User>> OnUserDeleted;
|
||||
|
||||
public event EventHandler<GenericEventArgs<Data.Entities.User>> OnUserLockedOut;
|
||||
public event EventHandler<GenericEventArgs<User>> OnUserLockedOut;
|
||||
|
||||
public IEnumerable<Data.Entities.User> Users
|
||||
public IEnumerable<User> Users
|
||||
{
|
||||
get
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users;
|
||||
}
|
||||
}
|
||||
|
@ -73,37 +75,38 @@ namespace Jellyfin.Server.Implementations.User
|
|||
{
|
||||
get
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
return dbContext.Users.Select(u => u.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public Data.Entities.User GetUserById(Guid id)
|
||||
public User GetUserById(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
return dbContext.Users.Find(id);
|
||||
}
|
||||
|
||||
public Data.Entities.User GetUserByName(string name)
|
||||
public User GetUserByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
throw new ArgumentException("Invalid username", nameof(name));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
return dbContext.Users.FirstOrDefault(u =>
|
||||
string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
|
||||
// This can't use an overload with StringComparer because that would cause the query to
|
||||
// have to be evaluated client-side.
|
||||
return dbContext.Users.FirstOrDefault(u => string.Equals(u.Username, name));
|
||||
}
|
||||
|
||||
public async Task RenameUser(Data.Entities.User user, string newName)
|
||||
public async Task RenameUser(User user, string newName)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
|
@ -132,43 +135,50 @@ namespace Jellyfin.Server.Implementations.User
|
|||
user.Username = newName;
|
||||
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||
|
||||
OnUserUpdated?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user));
|
||||
OnUserUpdated?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}
|
||||
|
||||
public void UpdateUser(Data.Entities.User user)
|
||||
public void UpdateUser(User user)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
dbContext.Users.Update(user);
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
public async Task UpdateUserAsync(Data.Entities.User user)
|
||||
public async Task UpdateUserAsync(User user)
|
||||
{
|
||||
await using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
dbContext.Users.Update(user);
|
||||
|
||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public Data.Entities.User CreateUser(string name)
|
||||
public User CreateUser(string name)
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
if (!IsValidUsername(name))
|
||||
{
|
||||
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
|
||||
}
|
||||
|
||||
var newUser = CreateUserObject(name);
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
var newUser = new User(name, _defaultAuthenticationProvider.GetType().FullName);
|
||||
dbContext.Users.Add(newUser);
|
||||
dbContext.SaveChanges();
|
||||
|
||||
OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser));
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
public void DeleteUser(Data.Entities.User user)
|
||||
public void DeleteUser(User user)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
if (!dbContext.Users.Contains(user))
|
||||
{
|
||||
|
@ -200,19 +210,20 @@ namespace Jellyfin.Server.Implementations.User
|
|||
|
||||
dbContext.Users.Remove(user);
|
||||
dbContext.SaveChanges();
|
||||
OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}
|
||||
|
||||
public Task ResetPassword(Data.Entities.User user)
|
||||
public Task ResetPassword(User user)
|
||||
{
|
||||
return ChangePassword(user, string.Empty);
|
||||
}
|
||||
|
||||
public void ResetEasyPassword(Data.Entities.User user)
|
||||
public void ResetEasyPassword(User user)
|
||||
{
|
||||
ChangeEasyPassword(user, string.Empty, null);
|
||||
}
|
||||
|
||||
public async Task ChangePassword(Data.Entities.User user, string newPassword)
|
||||
public async Task ChangePassword(User user, string newPassword)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
|
@ -222,24 +233,18 @@ namespace Jellyfin.Server.Implementations.User
|
|||
await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false);
|
||||
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||
|
||||
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user));
|
||||
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}
|
||||
|
||||
public void ChangeEasyPassword(Data.Entities.User user, string newPassword, string newPasswordSha1)
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordSha1)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1);
|
||||
|
||||
UpdateUser(user);
|
||||
|
||||
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user));
|
||||
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
}
|
||||
|
||||
public UserDto GetUserDto(Data.Entities.User user, string remoteEndPoint = null)
|
||||
public UserDto GetUserDto(User user, string remoteEndPoint = null)
|
||||
{
|
||||
return new UserDto
|
||||
{
|
||||
|
@ -271,7 +276,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
MaxParentalRating = user.MaxParentalAgeRating,
|
||||
EnableUserPreferenceAccess = user.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = user.RemoteClientBitrateLimit.GetValueOrDefault(),
|
||||
AuthenticatioIsnProviderId = user.AuthenticationProviderId,
|
||||
AuthenticationProviderId = user.AuthenticationProviderId,
|
||||
PasswordResetProviderId = user.PasswordResetProviderId,
|
||||
InvalidLoginAttemptCount = user.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = user.LoginAttemptsBeforeLockout.GetValueOrDefault(),
|
||||
|
@ -306,7 +311,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
};
|
||||
}
|
||||
|
||||
public PublicUserDto GetPublicUserDto(Data.Entities.User user, string remoteEndPoint = null)
|
||||
public PublicUserDto GetPublicUserDto(User user, string remoteEndPoint = null)
|
||||
{
|
||||
if (user == null)
|
||||
{
|
||||
|
@ -328,7 +333,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
};
|
||||
}
|
||||
|
||||
public async Task<Data.Entities.User> AuthenticateUser(
|
||||
public async Task<User> AuthenticateUser(
|
||||
string username,
|
||||
string password,
|
||||
string passwordSha1,
|
||||
|
@ -341,7 +346,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
throw new ArgumentNullException(nameof(username));
|
||||
}
|
||||
|
||||
var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
|
||||
var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
|
||||
bool success;
|
||||
IAuthenticationProvider authenticationProvider;
|
||||
|
||||
|
@ -370,7 +375,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
// Search the database for the user again
|
||||
// the authentication provider might have created it
|
||||
user = Users
|
||||
.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
|
||||
.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
|
||||
{
|
||||
|
@ -436,10 +441,10 @@ namespace Jellyfin.Server.Implementations.User
|
|||
if (isUserSession)
|
||||
{
|
||||
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
||||
UpdateUser(user);
|
||||
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
ResetInvalidLoginAttemptCount(user);
|
||||
user.InvalidLoginAttemptCount = 0;
|
||||
_logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Username);
|
||||
}
|
||||
else
|
||||
|
@ -495,14 +500,11 @@ namespace Jellyfin.Server.Implementations.User
|
|||
public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
|
||||
{
|
||||
_authenticationProviders = authenticationProviders.ToArray();
|
||||
|
||||
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
||||
|
||||
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
||||
|
||||
_passwordResetProviders = passwordResetProviders.ToArray();
|
||||
|
||||
_defaultPasswordResetProvider = passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
||||
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
||||
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||
}
|
||||
|
||||
public NameIdPair[] GetAuthenticationProviders()
|
||||
|
@ -563,7 +565,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
user.MaxParentalAgeRating = policy.MaxParentalRating;
|
||||
user.EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess;
|
||||
user.RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit;
|
||||
user.AuthenticationProviderId = policy.AuthenticatioIsnProviderId;
|
||||
user.AuthenticationProviderId = policy.AuthenticationProviderId;
|
||||
user.PasswordResetProviderId = policy.PasswordResetProviderId;
|
||||
user.InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount;
|
||||
user.LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1
|
||||
|
@ -604,28 +606,25 @@ namespace Jellyfin.Server.Implementations.User
|
|||
user.SetPreference(PreferenceKind.EnableContentDeletionFromFolders, policy.EnableContentDeletionFromFolders);
|
||||
}
|
||||
|
||||
private Data.Entities.User CreateUserObject(string name)
|
||||
private bool IsValidUsername(string name)
|
||||
{
|
||||
return new Data.Entities.User(
|
||||
username: name,
|
||||
mustUpdatePassword: false,
|
||||
authenticationProviderId: _defaultAuthenticationProvider.GetType().FullName,
|
||||
invalidLoginAttemptCount: -1,
|
||||
subtitleMode: SubtitlePlaybackMode.Default,
|
||||
playDefaultAudioTrack: true);
|
||||
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
||||
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
||||
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), and periods (.)
|
||||
return Regex.IsMatch(name, @"^[\w\-'._@]*$");
|
||||
}
|
||||
|
||||
private IAuthenticationProvider GetAuthenticationProvider(Data.Entities.User user)
|
||||
private IAuthenticationProvider GetAuthenticationProvider(User user)
|
||||
{
|
||||
return GetAuthenticationProviders(user)[0];
|
||||
}
|
||||
|
||||
private IPasswordResetProvider GetPasswordResetProvider(Data.Entities.User user)
|
||||
private IPasswordResetProvider GetPasswordResetProvider(User user)
|
||||
{
|
||||
return GetPasswordResetProviders(user)[0];
|
||||
}
|
||||
|
||||
private IList<IAuthenticationProvider> GetAuthenticationProviders(Data.Entities.User user)
|
||||
private IList<IAuthenticationProvider> GetAuthenticationProviders(User user)
|
||||
{
|
||||
var authenticationProviderId = user?.AuthenticationProviderId;
|
||||
|
||||
|
@ -640,7 +639,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
{
|
||||
// Assign the user to the InvalidAuthProvider since no configured auth provider was valid/found
|
||||
_logger.LogWarning(
|
||||
"User {UserName} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected",
|
||||
"User {Username} was found with invalid/missing Authentication Provider {AuthenticationProviderId}. Assigning user to InvalidAuthProvider until this is corrected",
|
||||
user?.Username,
|
||||
user?.AuthenticationProviderId);
|
||||
providers = new List<IAuthenticationProvider>
|
||||
|
@ -652,7 +651,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
return providers;
|
||||
}
|
||||
|
||||
private IList<IPasswordResetProvider> GetPasswordResetProviders(Data.Entities.User user)
|
||||
private IList<IPasswordResetProvider> GetPasswordResetProviders(User user)
|
||||
{
|
||||
var passwordResetProviderId = user?.PasswordResetProviderId;
|
||||
var providers = _passwordResetProviders.Where(i => i.IsEnabled).ToArray();
|
||||
|
@ -675,11 +674,10 @@ namespace Jellyfin.Server.Implementations.User
|
|||
return providers;
|
||||
}
|
||||
|
||||
private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)>
|
||||
AuthenticateLocalUser(
|
||||
private async Task<(IAuthenticationProvider authenticationProvider, string username, bool success)> AuthenticateLocalUser(
|
||||
string username,
|
||||
string password,
|
||||
Jellyfin.Data.Entities.User user,
|
||||
User user,
|
||||
string remoteEndPoint)
|
||||
{
|
||||
bool success = false;
|
||||
|
@ -721,7 +719,7 @@ namespace Jellyfin.Server.Implementations.User
|
|||
IAuthenticationProvider provider,
|
||||
string username,
|
||||
string password,
|
||||
Data.Entities.User resolvedUser)
|
||||
User resolvedUser)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -745,27 +743,21 @@ namespace Jellyfin.Server.Implementations.User
|
|||
}
|
||||
}
|
||||
|
||||
private void IncrementInvalidLoginAttemptCount(Data.Entities.User user)
|
||||
private void IncrementInvalidLoginAttemptCount(User user)
|
||||
{
|
||||
int invalidLogins = user.InvalidLoginAttemptCount;
|
||||
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
|
||||
if (maxInvalidLogins.HasValue
|
||||
&& invalidLogins >= maxInvalidLogins)
|
||||
if (maxInvalidLogins.HasValue && invalidLogins >= maxInvalidLogins)
|
||||
{
|
||||
user.SetPermission(PermissionKind.IsDisabled, true);
|
||||
OnUserLockedOut?.Invoke(this, new GenericEventArgs<Data.Entities.User>(user));
|
||||
OnUserLockedOut?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
_logger.LogWarning(
|
||||
"Disabling user {UserName} due to {Attempts} unsuccessful login attempts.",
|
||||
"Disabling user {Username} due to {Attempts} unsuccessful login attempts.",
|
||||
user.Username,
|
||||
invalidLogins);
|
||||
}
|
||||
|
||||
UpdateUser(user);
|
||||
}
|
||||
|
||||
private void ResetInvalidLoginAttemptCount(Data.Entities.User user)
|
||||
{
|
||||
user.InvalidLoginAttemptCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,8 +7,10 @@ using Emby.Server.Implementations;
|
|||
using Jellyfin.Drawing.Skia;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
@ -69,6 +71,7 @@ namespace Jellyfin.Server
|
|||
serviceCollection.AddSingleton<JellyfinDbProvider>();
|
||||
|
||||
serviceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||
serviceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
|
||||
base.RegisterServices(serviceCollection);
|
||||
}
|
||||
|
@ -80,6 +83,9 @@ namespace Jellyfin.Server
|
|||
protected override IEnumerable<Assembly> GetAssembliesWithPartsInternal()
|
||||
{
|
||||
yield return typeof(CoreAppHost).Assembly;
|
||||
yield return typeof(DefaultAuthenticationProvider).Assembly;
|
||||
yield return typeof(DefaultPasswordResetProvider).Assembly;
|
||||
yield return typeof(InvalidAuthProvider).Assembly;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.7.82" />
|
||||
<PackageReference Include="Json.Net" Version="1.0.22" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.3" />
|
||||
<PackageReference Include="prometheus-net" Version="3.5.0" />
|
||||
|
|
|
@ -1,33 +1,45 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using SQLitePCL.pretty;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// The migration routine for migrating the user database to EF Core.
|
||||
/// </summary>
|
||||
public class MigrateUserDb : IMigrationRoutine
|
||||
{
|
||||
private const string DbFilename = "users.db";
|
||||
|
||||
private readonly ILogger<MigrateUserDb> _logger;
|
||||
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
|
||||
private readonly JellyfinDbProvider _provider;
|
||||
|
||||
private readonly MyXmlSerializer _xmlSerializer;
|
||||
|
||||
public MigrateUserDb(ILogger<MigrateUserDb> logger, IServerApplicationPaths paths, JellyfinDbProvider provider, MyXmlSerializer xmlSerializer)
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="paths">The server application paths.</param>
|
||||
/// <param name="provider">The database provider.</param>
|
||||
/// <param name="xmlSerializer">The xml serializer.</param>
|
||||
public MigrateUserDb(
|
||||
ILogger<MigrateUserDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
JellyfinDbProvider provider,
|
||||
MyXmlSerializer xmlSerializer)
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
|
@ -35,18 +47,21 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
_xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Guid Id => Guid.Parse("5C4B82A2-F053-4009-BD05-B6FCAD82F14C");
|
||||
|
||||
public string Name => "MigrateUserDb";
|
||||
/// <inheritdoc/>
|
||||
public string Name => "MigrateUserDatabase";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _paths.DataPath;
|
||||
_logger.LogInformation("Migrating the user database may take a while, do not stop Jellyfin.");
|
||||
|
||||
using (var connection = SQLite3.Open(Path.Combine(dataPath, "users.db"), ConnectionFlags.ReadOnly, null))
|
||||
using (var connection = SQLite3.Open(Path.Combine(dataPath, DbFilename), ConnectionFlags.ReadOnly, null))
|
||||
{
|
||||
using var dbContext = _provider.CreateContext();
|
||||
var dbContext = _provider.CreateContext();
|
||||
|
||||
var queryResult = connection.Query("SELECT * FROM LocalUsersv2");
|
||||
|
||||
|
@ -55,26 +70,30 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
|
||||
foreach (var entry in queryResult)
|
||||
{
|
||||
var json = JsonConvert.DeserializeObject<Dictionary<string, string>>(entry[2].ToString());
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, json["Name"]);
|
||||
UserMockup mockup = JsonSerializer.Deserialize<UserMockup>(entry[2].ToBlob());
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
|
||||
|
||||
var config = (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"));
|
||||
var policy = (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"));
|
||||
var config = File.Exists(Path.Combine(userDataDir, "config.xml"))
|
||||
? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"))
|
||||
: new UserConfiguration();
|
||||
var policy = File.Exists(Path.Combine(userDataDir, "policy.xml"))
|
||||
? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"))
|
||||
: new UserPolicy();
|
||||
policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
|
||||
"Emby.Server.Implementations.Library",
|
||||
"Jellyfin.Server.Implementations.Users",
|
||||
StringComparison.Ordinal)
|
||||
?? typeof(DefaultAuthenticationProvider).FullName;
|
||||
|
||||
var user = new User(
|
||||
json["Name"],
|
||||
false,
|
||||
policy.AuthenticatioIsnProviderId,
|
||||
policy.InvalidLoginAttemptCount,
|
||||
config.SubtitleMode,
|
||||
config.PlayDefaultAudioTrack)
|
||||
policy.PasswordResetProviderId ??= typeof(DefaultPasswordResetProvider).FullName;
|
||||
|
||||
var user = new User(mockup.Name, policy.AuthenticationProviderId)
|
||||
{
|
||||
Id = entry[1].ReadGuidFromBlob(),
|
||||
InternalId = entry[0].ToInt64(),
|
||||
MaxParentalAgeRating = policy.MaxParentalRating,
|
||||
EnableUserPreferenceAccess = policy.EnableUserPreferenceAccess,
|
||||
RemoteClientBitrateLimit = policy.RemoteClientBitrateLimit,
|
||||
AuthenticationProviderId = policy.AuthenticatioIsnProviderId,
|
||||
PasswordResetProviderId = policy.PasswordResetProviderId,
|
||||
InvalidLoginAttemptCount = policy.InvalidLoginAttemptCount,
|
||||
LoginAttemptsBeforeLockout = policy.LoginAttemptsBeforeLockout == -1 ? null : new int?(policy.LoginAttemptsBeforeLockout),
|
||||
|
@ -89,6 +108,10 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
EnableNextEpisodeAutoPlay = config.EnableNextEpisodeAutoPlay,
|
||||
RememberSubtitleSelections = config.RememberSubtitleSelections,
|
||||
SubtitleLanguagePreference = config.SubtitleLanguagePreference,
|
||||
Password = mockup.Password,
|
||||
EasyPassword = mockup.EasyPassword,
|
||||
LastLoginDate = mockup.LastLoginDate ?? DateTime.MinValue,
|
||||
LastActivityDate = mockup.LastActivityDate ?? DateTime.MinValue
|
||||
};
|
||||
|
||||
user.SetPermission(PermissionKind.IsAdministrator, policy.IsAdministrator);
|
||||
|
@ -112,6 +135,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing);
|
||||
user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding);
|
||||
user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing);
|
||||
|
||||
foreach (var policyAccessSchedule in policy.AccessSchedules)
|
||||
{
|
||||
user.AccessSchedules.Add(policyAccessSchedule);
|
||||
|
@ -126,6 +150,8 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
user.SetPreference(PreferenceKind.GroupedFolders, config.GroupedFolders);
|
||||
user.SetPreference(PreferenceKind.MyMediaExcludes, config.MyMediaExcludes);
|
||||
user.SetPreference(PreferenceKind.LatestItemExcludes, config.LatestItemsExcludes);
|
||||
|
||||
dbContext.Users.Add(user);
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
|
@ -133,12 +159,32 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||
|
||||
try
|
||||
{
|
||||
File.Move(Path.Combine(dataPath, "users.db"), Path.Combine(dataPath, "users.db" + ".old"));
|
||||
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
|
||||
|
||||
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
|
||||
if (File.Exists(journalPath))
|
||||
{
|
||||
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.LogError(e, "Error renaming legacy user database to 'users.db.old'");
|
||||
}
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal class UserMockup
|
||||
{
|
||||
public string Password { get; set; }
|
||||
|
||||
public string EasyPassword { get; set; }
|
||||
|
||||
public DateTime? LastLoginDate { get; set; }
|
||||
|
||||
public DateTime? LastActivityDate { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
|
@ -34,7 +36,7 @@ namespace MediaBrowser.Model.Users
|
|||
|
||||
public string[] BlockedTags { get; set; }
|
||||
public bool EnableUserPreferenceAccess { get; set; }
|
||||
public Jellyfin.Data.Entities.AccessSchedule[] AccessSchedules { get; set; }
|
||||
public AccessSchedule[] AccessSchedules { get; set; }
|
||||
public UnratedItem[] BlockUnratedItems { get; set; }
|
||||
public bool EnableRemoteControlOfOtherUsers { get; set; }
|
||||
public bool EnableSharedDeviceControl { get; set; }
|
||||
|
@ -78,7 +80,9 @@ namespace MediaBrowser.Model.Users
|
|||
public string[] BlockedChannels { get; set; }
|
||||
|
||||
public int RemoteClientBitrateLimit { get; set; }
|
||||
public string AuthenticatioIsnProviderId { get; set; }
|
||||
|
||||
[XmlElement(ElementName = "AuthenticationProviderId")]
|
||||
public string AuthenticationProviderId { get; set; }
|
||||
public string PasswordResetProviderId { get; set; }
|
||||
|
||||
public UserPolicy()
|
||||
|
|
Loading…
Reference in New Issue
Block a user