diff --git a/RoadieApi/Controllers/AccountController.cs b/RoadieApi/Controllers/AccountController.cs index e4506ed..ce38058 100644 --- a/RoadieApi/Controllers/AccountController.cs +++ b/RoadieApi/Controllers/AccountController.cs @@ -22,11 +22,11 @@ namespace Roadie.Api.Controllers [AllowAnonymous] public class AccountController : ControllerBase { - private readonly IConfiguration configuration; - private readonly ILogger logger; - private readonly SignInManager signInManager; - private readonly ITokenService tokenService; - private readonly UserManager userManager; + private readonly IConfiguration Configuration; + private readonly ILogger Logger; + private readonly SignInManager SignInManager; + private readonly ITokenService TokenService; + private readonly UserManager UserManager; private IRoadieSettings RoadieSettings { get; } private ICacheManager CacheManager { get; } private IAdminService AdminService { get; } @@ -40,11 +40,11 @@ namespace Roadie.Api.Controllers ITokenService tokenService, ICacheManager cacheManager) { - this.userManager = userManager; - this.signInManager = signInManager; - this.configuration = configuration; - this.logger = logger; - this.tokenService = tokenService; + this.UserManager = userManager; + this.SignInManager = signInManager; + this.Configuration = configuration; + this.Logger = logger; + this.TokenService = tokenService; this.CacheManager = cacheManager; this.RoadieSettings = new RoadieSettings(); @@ -61,19 +61,19 @@ namespace Roadie.Api.Controllers try { // Login user - var loginResult = await signInManager.PasswordSignInAsync(model.Username, model.Password, isPersistent: false, lockoutOnFailure: false); + var loginResult = await SignInManager.PasswordSignInAsync(model.Username, model.Password, isPersistent: false, lockoutOnFailure: false); if (!loginResult.Succeeded) { return BadRequest(); } - var user = await userManager.FindByNameAsync(model.Username); + var user = await UserManager.FindByNameAsync(model.Username); var now = DateTime.UtcNow; user.LastLogin = now; user.LastApiAccess = now; user.LastUpdated = now; - await userManager.UpdateAsync(user); - var t = await this.tokenService.GenerateToken(user, this.userManager); - this.logger.LogInformation($"Successfully authenticated User [{ model.Username}]"); + await UserManager.UpdateAsync(user); + var t = await this.TokenService.GenerateToken(user, this.UserManager); + this.Logger.LogInformation($"Successfully authenticated User [{ model.Username}]"); this.CacheManager.ClearRegion(EntityControllerBase.ControllerCacheRegionUrn); var avatarUrl = $"{this.Request.Scheme}://{this.Request.Host}/images/user/{ user.RoadieId }/{ this.RoadieSettings.ThumbnailImageSize.Width }/{ this.RoadieSettings.ThumbnailImageSize.Height }"; return Ok(new @@ -89,7 +89,7 @@ namespace Roadie.Api.Controllers } catch (Exception ex) { - this.logger.LogError(ex, "Eror in CreateToken"); + this.Logger.LogError(ex, "Eror in CreateToken"); return BadRequest(); } } @@ -106,8 +106,8 @@ namespace Roadie.Api.Controllers if (!String.IsNullOrWhiteSpace(username)) { - var user = await userManager.FindByNameAsync(username); - return Ok(await this.tokenService.GenerateToken(user, this.userManager)); + var user = await UserManager.FindByNameAsync(username); + return Ok(await this.TokenService.GenerateToken(user, this.UserManager)); } else { @@ -126,19 +126,20 @@ namespace Roadie.Api.Controllers var user = new ApplicationUser { UserName = registerModel.Username, + RegisteredOn = DateTime.UtcNow, Email = registerModel.Email }; - var identityResult = await this.userManager.CreateAsync(user, registerModel.Password); + var identityResult = await this.UserManager.CreateAsync(user, registerModel.Password); if (identityResult.Succeeded) { if(user.Id == 1) { - await this.AdminService.DoInitialSetup(user, this.userManager); + await this.AdminService.DoInitialSetup(user, this.UserManager); } - await signInManager.SignInAsync(user, isPersistent: false); - var t = await this.tokenService.GenerateToken(user, this.userManager); - this.logger.LogInformation($"Successfully authenticated User [{ registerModel.Username}]"); + await SignInManager.SignInAsync(user, isPersistent: false); + var t = await this.TokenService.GenerateToken(user, this.UserManager); + this.Logger.LogInformation($"Successfully created and authenticated User [{ registerModel.Username}]"); this.CacheManager.ClearRegion(EntityControllerBase.ControllerCacheRegionUrn); var avatarUrl = $"{this.Request.Scheme}://{this.Request.Host}/images/user/{ user.RoadieId }/{ this.RoadieSettings.ThumbnailImageSize.Width }/{ this.RoadieSettings.ThumbnailImageSize.Height }"; return Ok(new @@ -172,12 +173,12 @@ namespace Roadie.Api.Controllers CreatedDate = DateTime.UtcNow }; - var identityResult = await this.userManager.ResetPasswordAsync(user, resetPasswordModel.Token, resetPasswordModel.Password); + var identityResult = await this.UserManager.ResetPasswordAsync(user, resetPasswordModel.Token, resetPasswordModel.Password); if (identityResult.Succeeded) { this.CacheManager.ClearRegion(EntityControllerBase.ControllerCacheRegionUrn); - await signInManager.SignInAsync(user, isPersistent: false); - return Ok(this.tokenService.GenerateToken(user, this.userManager)); + await SignInManager.SignInAsync(user, isPersistent: false); + return Ok(this.TokenService.GenerateToken(user, this.UserManager)); } else { diff --git a/RoadieApi/Controllers/UserController.cs b/RoadieApi/Controllers/UserController.cs index d6b6dd8..c98aed0 100644 --- a/RoadieApi/Controllers/UserController.cs +++ b/RoadieApi/Controllers/UserController.cs @@ -7,6 +7,7 @@ using Roadie.Api.Services; using Roadie.Library.Caching; using Roadie.Library.Identity; using Roadie.Library.Models.Pagination; +using Roadie.Library.Models.Users; using System; using System.Net; using System.Net.Http; @@ -21,41 +22,73 @@ namespace Roadie.Api.Controllers public class UserController : EntityControllerBase { private IUserService UserService { get; } + private readonly ITokenService TokenService; - public UserController(IUserService userService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager userManager) + public UserController(IUserService userService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, ITokenService tokenService, UserManager userManager) : base(cacheManager, configuration, userManager) { - this.Logger = logger.CreateLogger("RoadieApi.Controllers.LabelController"); + this.Logger = logger.CreateLogger("RoadieApi.Controllers.UserController"); this.UserService = userService; + this.TokenService = tokenService; } - //[EnableQuery] - //public IActionResult Get() - //{ - // return Ok(this._RoadieDbContext.Labels.ProjectToType()); - //} + [HttpGet("{id}")] + [ProducesResponseType(200)] + [ProducesResponseType(204)] + [ProducesResponseType(404)] + public async Task Get(Guid id) + { + var user = await this.CurrentUserModel(); + var result = await this.CacheManager.GetAsync($"urn:user_model_by_id:{ id }", async () => + { + return await this.UserService.ById(user, id); + }, ControllerCacheRegionUrn); + if (result == null || result.IsNotFoundResult) + { + return NotFound(); + } + if (!result.IsSuccess) + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } + return Ok(result); + } - //[HttpGet("{id}")] - //[ProducesResponseType(200)] - //[ProducesResponseType(404)] - //public IActionResult Get(Guid id) - //{ - // var key = id.ToString(); - // var result = this._cacheManager.Get(key, () => - // { - // var d = this._RoadieDbContext.Labels.FirstOrDefault(x => x.RoadieId == id); - // if (d != null) - // { - // return d.Adapt(); - // } - // return null; - // }, key); - // if (result == null) - // { - // return NotFound(); - // } - // return Ok(result); - //} + [HttpPost("profile/edit")] + [ProducesResponseType(200)] + public async Task UpdateProfile(User model) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + var user = await this.CurrentUserModel(); + var result = await this.UserService.UpdateProfile(user, model); + if (result == null || result.IsNotFoundResult) + { + return NotFound(); + } + if (!result.IsSuccess) + { + return StatusCode((int)HttpStatusCode.InternalServerError); + } + this.CacheManager.ClearRegion(ControllerCacheRegionUrn); + var modelUser = await UserManager.FindByNameAsync(model.UserName); + var t = await this.TokenService.GenerateToken(modelUser, this.UserManager); + this.CacheManager.ClearRegion(EntityControllerBase.ControllerCacheRegionUrn); + var avatarUrl = $"{this.Request.Scheme}://{this.Request.Host}/images/user/{ modelUser.RoadieId }/{ this.RoadieSettings.ThumbnailImageSize.Width }/{ this.RoadieSettings.ThumbnailImageSize.Height }"; + return Ok(new + { + IsSuccess = true, + Username = modelUser.UserName, + modelUser.Email, + modelUser.LastLogin, + avatarUrl, + Token = t, + modelUser.Timeformat, + modelUser.Timezone + }); + } [HttpPost("setArtistRating/{releaseId}/{rating}")] diff --git a/RoadieApi/ModelBinding/SubsonicRequestBinder.cs b/RoadieApi/ModelBinding/SubsonicRequestBinder.cs index d06cfa3..1522cda 100644 --- a/RoadieApi/ModelBinding/SubsonicRequestBinder.cs +++ b/RoadieApi/ModelBinding/SubsonicRequestBinder.cs @@ -109,9 +109,9 @@ namespace Roadie.Api.ModelBinding var model = new SubsonicRequest { - AlbumCount = SafeParser.ToNumber(modelDictionary["albumCount"]) ?? 20, + AlbumCount = SafeParser.ToNumber(modelDictionary["albumCount"]) ?? 20, AlbumOffset = SafeParser.ToNumber(modelDictionary["albumOffset"]), - ArtistCount = SafeParser.ToNumber(modelDictionary["artistCount"]) ?? 20, + ArtistCount = SafeParser.ToNumber(modelDictionary["artistCount"]) ?? 20, ArtistName = SafeParser.ToString(modelDictionary["artist"]), ArtistOffset = SafeParser.ToNumber(modelDictionary["artistOffset"]), c = SafeParser.ToString(modelDictionary["c"]), @@ -127,8 +127,8 @@ namespace Roadie.Api.ModelBinding p = SafeParser.ToString(modelDictionary["p"]), Query = SafeParser.ToString(modelDictionary["query"]), s = SafeParser.ToString(modelDictionary["s"]), - Size = SafeParser.ToNumber(modelDictionary["size"]), - SongCount = SafeParser.ToNumber(modelDictionary["songCount"]) ?? 20, + Size = SafeParser.ToNumber(modelDictionary["size"]), + SongCount = SafeParser.ToNumber(modelDictionary["songCount"]) ?? 20, SongOffset = SafeParser.ToNumber(modelDictionary["songOffset"]), t = SafeParser.ToString(modelDictionary["t"]), ToYear = SafeParser.ToNumber(modelDictionary["toYear"]), diff --git a/RoadieApi/Services/IUserService.cs b/RoadieApi/Services/IUserService.cs index 7c6936c..dc81f4f 100644 --- a/RoadieApi/Services/IUserService.cs +++ b/RoadieApi/Services/IUserService.cs @@ -1,4 +1,5 @@ using Roadie.Library; +using Roadie.Library.Identity; using Roadie.Library.Models.Pagination; using Roadie.Library.Models.Users; using System; @@ -8,6 +9,10 @@ namespace Roadie.Api.Services { public interface IUserService { + Task> ById(User user, Guid id); + + Task> UpdateProfile(User userPerformingUpdate, User userBeingUpdatedModel); + Task> List(PagedRequest request); Task> SetArtistBookmark(Guid artistId, User roadieUser, bool isBookmarked); @@ -31,5 +36,6 @@ namespace Roadie.Api.Services Task> SetTrackBookmark(Guid trackId, User roadieUser, bool isBookmarked); Task> SetTrackRating(Guid trackId, User roadieUser, short rating); + } } \ No newline at end of file diff --git a/RoadieApi/Services/SubsonicService.cs b/RoadieApi/Services/SubsonicService.cs index a608ad9..f3bdff6 100644 --- a/RoadieApi/Services/SubsonicService.cs +++ b/RoadieApi/Services/SubsonicService.cs @@ -172,7 +172,7 @@ namespace Roadie.Api.Services { BookmarkTargetId = track.Id, BookmarkType = Library.Enums.BookmarkType.Track, - UserId = roadieUser.Id, + UserId = roadieUser.Id.Value, Comment = comment, Position = position }; @@ -1691,7 +1691,7 @@ namespace Roadie.Api.Services } var chatMessage = new data.ChatMessage { - UserId = roadieUser.Id, + UserId = roadieUser.Id.Value, Message = request.Message }; this.DbContext.ChatMessages.Add(chatMessage); @@ -1817,7 +1817,7 @@ namespace Roadie.Api.Services { // Indexes for "Music" Artists alphabetically pagedRequest.SkipValue = 0; - pagedRequest.Limit = int.MaxValue; + pagedRequest.Limit = short.MaxValue; pagedRequest.Sort = "Artist.Text"; var artistList = await this.ArtistService.List(roadieUser: roadieUser, request: pagedRequest, @@ -1898,7 +1898,7 @@ namespace Roadie.Api.Services // Indexes for Artists alphabetically var pagedRequest = request.PagedRequest; pagedRequest.SkipValue = 0; - pagedRequest.Limit = int.MaxValue; + pagedRequest.Limit = short.MaxValue; pagedRequest.Sort = "Artist.Text"; var artistList = await this.ArtistService.List(roadieUser: roadieUser, request: pagedRequest, diff --git a/RoadieApi/Services/TokenService.cs b/RoadieApi/Services/TokenService.cs index 3fc32b0..954b5f8 100644 --- a/RoadieApi/Services/TokenService.cs +++ b/RoadieApi/Services/TokenService.cs @@ -29,11 +29,12 @@ namespace Roadie.Api.Services var claims = new Claim[] { - new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), - new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), - new Claim(JwtRegisteredClaimNames.Email, user.Email), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - new Claim(JwtRegisteredClaimNames.Iat, utcNow.ToString()) + new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), + new Claim("roadie_id", user.RoadieId.ToString()), + new Claim(JwtRegisteredClaimNames.UniqueName, user.UserName), + new Claim(JwtRegisteredClaimNames.Email, user.Email), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + new Claim(JwtRegisteredClaimNames.Iat, utcNow.ToString()) }.Union(userRoles); var now = DateTime.UtcNow; diff --git a/RoadieApi/Services/UserService.cs b/RoadieApi/Services/UserService.cs index 4db4ece..e22aafd 100644 --- a/RoadieApi/Services/UserService.cs +++ b/RoadieApi/Services/UserService.cs @@ -1,10 +1,12 @@ -using Microsoft.EntityFrameworkCore; +using Mapster; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Roadie.Library; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Encoding; using Roadie.Library.Identity; +using Roadie.Library.Imaging; using Roadie.Library.Models; using Roadie.Library.Models.Pagination; using Roadie.Library.Models.Statistics; @@ -32,6 +34,142 @@ namespace Roadie.Api.Services { } + public async Task> ById(User user, Guid id) + { + var timings = new Dictionary(); + var tsw = new Stopwatch(); + + var sw = Stopwatch.StartNew(); + sw.Start(); + var cacheKey = string.Format("urn:user_by_id_operation:{0}", id); + var result = await this.CacheManager.GetAsync>(cacheKey, async () => + { + tsw.Restart(); + var rr = await this.UserByIdAction(id); + tsw.Stop(); + timings.Add("UserByIdAction", tsw.ElapsedMilliseconds); + return rr; + + }, ApplicationUser.CacheRegionUrn(id)); + sw.Stop(); + if (result?.Data != null) + { + result.Data.Avatar = this.MakeUserThumbnailImage(id); + } + timings.Add("operation", sw.ElapsedMilliseconds); + this.Logger.LogDebug("ById Timings: id [{0}]", id); + return new OperationResult(result.Messages) + { + Data = result?.Data, + Errors = result?.Errors, + IsNotFoundResult = result?.IsNotFoundResult ?? false, + IsSuccess = result?.IsSuccess ?? false, + OperationTime = sw.ElapsedMilliseconds + }; + } + + public async Task> UpdateProfile(User userPerformingUpdate, User userBeingUpdatedModel) + { + var user = this.DbContext.Users.FirstOrDefault(x => x.RoadieId == userBeingUpdatedModel.UserId); + if (user == null) + { + return new OperationResult(true, string.Format("User Not Found [{0}]", userBeingUpdatedModel.UserId)); + } + if (user.Id != userPerformingUpdate.Id && !userPerformingUpdate.IsAdmin) + { + return new OperationResult + { + Errors = new List { new Exception("Access Denied") } + }; + } + // Check concurrency stamp + if(user.ConcurrencyStamp != userBeingUpdatedModel.ConcurrencyStamp) + { + return new OperationResult + { + Errors = new List { new Exception("User data is stale.") } + }; + } + // Check that username (if changed) doesn't already exist + if (user.UserName != userBeingUpdatedModel.UserName) + { + var userByUsername = this.DbContext.Users.FirstOrDefault(x => x.NormalizedUserName == userBeingUpdatedModel.UserName.ToUpper()); + if (userByUsername != null) + { + return new OperationResult + { + Errors = new List { new Exception("Username already in use") } + }; + } + } + // Check that email (if changed) doesn't already exist + if (user.Email != userBeingUpdatedModel.Email) + { + var userByEmail = this.DbContext.Users.FirstOrDefault(x => x.NormalizedEmail == userBeingUpdatedModel.Email.ToUpper()); + if (userByEmail != null) + { + return new OperationResult + { + Errors = new List { new Exception("Email already in use") } + }; + } + } + user.UserName = userBeingUpdatedModel.UserName; + user.NormalizedUserName = userBeingUpdatedModel.UserName.ToUpper(); + user.Email = userBeingUpdatedModel.Email; + user.NormalizedEmail = userBeingUpdatedModel.Email.ToUpper(); + user.ApiToken = userBeingUpdatedModel.ApiToken; + user.Timezone = userBeingUpdatedModel.Timezone; + user.Timeformat = userBeingUpdatedModel.Timeformat; + user.PlayerTrackLimit = userBeingUpdatedModel.PlayerTrackLimit; + user.RandomReleaseLimit = userBeingUpdatedModel.RandomReleaseLimit; + user.RecentlyPlayedLimit = userBeingUpdatedModel.RecentlyPlayedLimit; + user.Profile = userBeingUpdatedModel.Profile; + user.DoUseHtmlPlayer = userBeingUpdatedModel.DoUseHtmlPlayer; + user.IsPrivate = userBeingUpdatedModel.IsPrivate; + user.LastUpdated = DateTime.UtcNow; + user.FtpUrl = userBeingUpdatedModel.FtpUrl; + user.FtpDirectory = userBeingUpdatedModel.FtpDirectory; + user.FtpUsername = userBeingUpdatedModel.FtpUsername; + user.FtpPassword = EncryptionHelper.Encrypt(userBeingUpdatedModel.FtpPassword, user.RoadieId.ToString()); + user.ConcurrencyStamp = Guid.NewGuid().ToString(); + + if(!string.IsNullOrEmpty(userBeingUpdatedModel.AvatarData)) + { + var imageData = ImageHelper.ImageDataFromUrl(userBeingUpdatedModel.AvatarData); + if(imageData != null) + { + user.Avatar = ImageHelper.ResizeImage(imageData, this.Configuration.ThumbnailImageSize.Width, this.Configuration.ThumbnailImageSize.Height); + } + } + + await this.DbContext.SaveChangesAsync(); + + this.CacheManager.ClearRegion(ApplicationUser.CacheRegionUrn(user.RoadieId)); + + this.Logger.LogInformation($"User `{ userPerformingUpdate }` modifed user `{ userBeingUpdatedModel }`"); + + return new OperationResult + { + IsSuccess = true, + Data = true + }; + } + + private async Task> UserByIdAction(Guid id) + { + var user = this.GetUser(id); + if (user == null) + { + return new OperationResult(true, string.Format("User Not Found [{0}]", id)); + } + return new OperationResult + { + IsSuccess = true, + Data = user.Adapt() + }; + } + public async Task> List(PagedRequest request) { var sw = new Stopwatch(); diff --git a/RoadieLibrary/Engines/ArtistLookupEngine.cs b/RoadieLibrary/Engines/ArtistLookupEngine.cs index 105ce94..accdcd5 100644 --- a/RoadieLibrary/Engines/ArtistLookupEngine.cs +++ b/RoadieLibrary/Engines/ArtistLookupEngine.cs @@ -180,7 +180,7 @@ namespace Roadie.Library.Engines sw.Stop(); if (artist == null || !artist.IsValid) { - this.Logger.LogInformation("ArtistFactory: Artist Not Found By Name [{0}]", ArtistName); + this.Logger.LogInformation("ArtistLookupEngine: Artist Not Found By Name [{0}]", ArtistName); if (doFindIfNotInDatabase) { OperationResult ArtistSearch = null; diff --git a/RoadieLibrary/Imaging/ImageHelper.cs b/RoadieLibrary/Imaging/ImageHelper.cs index 271cc52..247ccdd 100644 --- a/RoadieLibrary/Imaging/ImageHelper.cs +++ b/RoadieLibrary/Imaging/ImageHelper.cs @@ -93,5 +93,19 @@ namespace Roadie.Library.Imaging return outStream.ToArray(); } } + + public static byte[] ImageDataFromUrl(string imageUrl) + { + if (!string.IsNullOrEmpty(imageUrl)) + { + var dataString = imageUrl.Trim().Replace('-', '+') + .Replace("data:image/jpeg;base64,", "") + .Replace("data:image/gif;base64,", "") + .Replace("data:image/png;base64,", ""); + return Convert.FromBase64String(dataString); + } + return null; + } + } } \ No newline at end of file diff --git a/RoadieLibrary/Models/Pagination/PagedRequest.cs b/RoadieLibrary/Models/Pagination/PagedRequest.cs index b48b09a..627e3b9 100644 --- a/RoadieLibrary/Models/Pagination/PagedRequest.cs +++ b/RoadieLibrary/Models/Pagination/PagedRequest.cs @@ -30,8 +30,8 @@ namespace Roadie.Library.Models.Pagination } public string Sort { get; set; } public string Order { get; set; } - public int? Limit { get; set; } = 10; - public int LimitValue + public short? Limit { get; set; } = 10; + public short LimitValue { get { diff --git a/RoadieLibrary/Models/Playlists/PlaylistList.cs b/RoadieLibrary/Models/Playlists/PlaylistList.cs index 37565e6..c15ed7c 100644 --- a/RoadieLibrary/Models/Playlists/PlaylistList.cs +++ b/RoadieLibrary/Models/Playlists/PlaylistList.cs @@ -12,7 +12,7 @@ namespace Roadie.Library.Models.Playlists public DataToken Playlist { get; set; } public DataToken User { get; set; } public Image Thumbnail { get; set; } - public int? PlaylistCount { get; set; } + public short? PlaylistCount { get; set; } public Image UserThumbnail { get; set; } public bool IsPublic { get; set; } public decimal? Duration { get; set; } diff --git a/RoadieLibrary/Models/ThirdPartyApi/Subsonic/Request.cs b/RoadieLibrary/Models/ThirdPartyApi/Subsonic/Request.cs index 38601fb..f7257c3 100644 --- a/RoadieLibrary/Models/ThirdPartyApi/Subsonic/Request.cs +++ b/RoadieLibrary/Models/ThirdPartyApi/Subsonic/Request.cs @@ -10,7 +10,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic { public const string ArtistIdIdentifier = "A:"; public const string CollectionIdentifier = "C:"; - public const int MaxPageSize = 100; + public const short MaxPageSize = 100; public const string PlaylistdIdentifier = "P:"; public const string ReleaseIdIdentifier = "R:"; public const string TrackIdIdentifier = "T:"; @@ -199,7 +199,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic /// /// Maximum number of albums to return. /// - public int? AlbumCount { get; set; } + public short? AlbumCount { get; set; } /// /// Search result offset for albums. Used for paging. @@ -209,7 +209,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic /// /// Maximum number of artists to return. /// - public int? ArtistCount { get; set; } + public short? ArtistCount { get; set; } /// /// Search result offset for artists. Used for paging. @@ -313,12 +313,12 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic /// Various *Count properties depending on objects being searched and client version. /// Something this value is posted as 'count' versus 'size' /// - public int? Size { get; set; } + public short? Size { get; set; } /// /// Maximum number of songs to return. /// - public int? SongCount { get; set; } + public short? SongCount { get; set; } /// /// Search result offset for songs. Used for paging. diff --git a/RoadieLibrary/Models/Users/User.cs b/RoadieLibrary/Models/Users/User.cs index 7b8a0ef..55d9e3b 100644 --- a/RoadieLibrary/Models/Users/User.cs +++ b/RoadieLibrary/Models/Users/User.cs @@ -1,23 +1,75 @@ using Mapster; using System; +using System.ComponentModel.DataAnnotations; namespace Roadie.Library.Models.Users { [Serializable] public class User - { + { public const string ActionKeyUserRated = "__userrated__"; - public bool IsEditor { get; set; } + [MaxLength(100)] + public string ApiToken { get; set; } + + public Image Avatar { get; set; } + + /// + /// Posted image from a client of selected new base64 encoded avatar for the user + /// + public string AvatarData { get; set; } + + public bool DoUseHtmlPlayer { get; set; } + + [Required] + [MaxLength(100)] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [MaxLength(500)] + public string FtpDirectory { get; set; } + + [MaxLength(500)] + public string FtpPassword { get; set; } + + [MaxLength(250)] + public string FtpUrl { get; set; } + + [MaxLength(50)] + public string FtpUsername { get; set; } + + [Required] + [MaxLength(100)] + public string ConcurrencyStamp { get; set; } + + public int? Id { get; set; } public bool IsAdmin { get; set; } - public string UserName { get; set; } + public bool IsEditor { get; set; } + public bool IsPrivate { get; set; } + + public short? PlayerTrackLimit { get; set; } + + [MaxLength(65535)] + public string Profile { get; set; } + + public short? RandomReleaseLimit { get; set; } + + public short? RecentlyPlayedLimit { get; set; } + + [StringLength(50)] + [Required] + public string Timeformat { get; set; } + + [MaxLength(50)] + [Required] + public string Timezone { get; set; } + [AdaptMember("RoadieId")] public Guid UserId { get; set; } - public int Id { get; set; } - public int? PlayerTrackLimit { get; set; } - public int? RecentlyPlayedLimit { get; set; } - public int? RandomReleaseLimit { get; set; } - public bool IsPrivate { get; set; } + + [Required] + [MaxLength(20)] + public string UserName { get; set; } public override string ToString() { diff --git a/RoadieLibrary/Processors/FolderProcessor.cs b/RoadieLibrary/Processors/FolderProcessor.cs index c2b9117..c253117 100644 --- a/RoadieLibrary/Processors/FolderProcessor.cs +++ b/RoadieLibrary/Processors/FolderProcessor.cs @@ -102,7 +102,7 @@ namespace Roadie.Library.Processors { "processedFiles", processedFiles }, { "newArtists", this.ArtistLookupEngine.AddedArtistIds.Count() }, { "newReleases", this.ReleaseLookupEngine.AddedReleaseIds.Count() }, - { "newTracks", this.ReleaseLookupEngine.AddedTrackIds.Count() } + { "newTracks", this.ReleaseFactory.AddedTrackIds.Count() } }, OperationTime = sw.ElapsedMilliseconds };