diff --git a/WatchIt.Common/WatchIt.Common.Model/Genders/Gender.cs b/WatchIt.Common/WatchIt.Common.Model/Genders/Gender.cs new file mode 100644 index 0000000..9cb7c2e --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Genders/Gender.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Genders; + +public class Gender +{ + #region PROPERTIES + + [JsonPropertyName("name")] + public required string Name { get; set; } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Genders/GenderResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Genders/GenderResponse.cs new file mode 100644 index 0000000..9677a0d --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Genders/GenderResponse.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Genders; + +public class GenderResponse : Gender +{ + #region PROPERTIES + + [JsonPropertyName("id")] + public required short? Id { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + [SetsRequiredMembers] + public GenderResponse(Database.Model.Common.Gender gender) + { + Id = gender.Id; + Name = gender.Name; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Genres/GenreQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Genres/GenreQueryParameters.cs index 9b2658c..220b5e4 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Genres/GenreQueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Genres/GenreQueryParameters.cs @@ -17,9 +17,9 @@ public class GenreQueryParameters : QueryParameters - #region PUBLIC METHODS + #region PRIVATE METHODS - public override bool IsMeetingConditions(GenreResponse item) => + protected override bool IsMeetingConditions(GenreResponse item) => ( TestStringWithRegex(item.Name, Name) && diff --git a/WatchIt.Common/WatchIt.Common.Model/Media/MediaQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Media/MediaQueryParameters.cs index 3977d12..fcbfe10 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Media/MediaQueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Media/MediaQueryParameters.cs @@ -59,9 +59,9 @@ public class MediaQueryParameters : QueryParameters - #region PUBLIC METHODS + #region PRIVATE METHODS - public override bool IsMeetingConditions(MediaResponse item) => + protected override bool IsMeetingConditions(MediaResponse item) => ( Test(item.Type, Type) && diff --git a/WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs index 4ee0ba9..fd10ae8 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Media/MediaResponse.cs @@ -51,7 +51,7 @@ public class MediaResponse : Media, IQueryOrderable ReleaseDate = media.ReleaseDate; Length = media.Length; Type = mediaType; - Rating = new RatingResponse(media.RatingMedia); + Rating = RatingResponse.Create(media.RatingMedia); } #endregion diff --git a/WatchIt.Common/WatchIt.Common.Model/Movies/MovieQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Movies/MovieQueryParameters.cs index aec1df9..ede62e0 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Movies/MovieQueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Movies/MovieQueryParameters.cs @@ -65,9 +65,9 @@ public class MovieQueryParameters : QueryParameters - #region PUBLIC METHODS + #region PRIVATE METHODS - public override bool IsMeetingConditions(MovieResponse item) => + protected override bool IsMeetingConditions(MovieResponse item) => ( TestStringWithRegex(item.Title, Title) && diff --git a/WatchIt.Common/WatchIt.Common.Model/Movies/MovieResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Movies/MovieResponse.cs index 78a230c..366b058 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Movies/MovieResponse.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Movies/MovieResponse.cs @@ -50,7 +50,7 @@ public class MovieResponse : Movie, IQueryOrderable ReleaseDate = mediaMovie.Media.ReleaseDate; Length = mediaMovie.Media.Length; Budget = mediaMovie.Budget; - Rating = new RatingResponse(mediaMovie.Media.RatingMedia); + Rating = RatingResponse.Create(mediaMovie.Media.RatingMedia); } #endregion diff --git a/WatchIt.Common/WatchIt.Common.Model/Persons/Person.cs b/WatchIt.Common/WatchIt.Common.Model/Persons/Person.cs new file mode 100644 index 0000000..0980294 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Persons/Person.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Persons; + +public class Person +{ + #region PROPERTIES + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("full_name")] + public string? FullName { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("birth_date")] + public DateOnly? BirthDate { get; set; } + + [JsonPropertyName("death_date")] + public DateOnly? DeathDate { get; set; } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Persons/PersonQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonQueryParameters.cs new file mode 100644 index 0000000..0cd03ed --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonQueryParameters.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Mvc; +using WatchIt.Common.Query; + +namespace WatchIt.Common.Model.Persons; + +public class PersonQueryParameters : QueryParameters +{ + #region PROPERTIES + + [FromQuery(Name = "name")] + public string? Name { get; set; } + + [FromQuery(Name = "full_name")] + public string? FullName { get; set; } + + [FromQuery(Name = "description")] + public string? Description { get; set; } + + [FromQuery(Name = "birth_date")] + public DateOnly? BirthDate { get; set; } + + [FromQuery(Name = "birth_date_from")] + public DateOnly? BirthDateFrom { get; set; } + + [FromQuery(Name = "birth_date_to")] + public DateOnly? BirthDateTo { get; set; } + + [FromQuery(Name = "death_date")] + public DateOnly? DeathDate { get; set; } + + [FromQuery(Name = "death_date_from")] + public DateOnly? DeathDateFrom { get; set; } + + [FromQuery(Name = "death_date_to")] + public DateOnly? DeathDateTo { get; set; } + + [FromQuery(Name = "gender_id")] + public short? GenderId { get; set; } + + [FromQuery(Name = "rating_average")] + public decimal? RatingAverage { get; set; } + + [FromQuery(Name = "rating_average_from")] + public decimal? RatingAverageFrom { get; set; } + + [FromQuery(Name = "rating_average_to")] + public decimal? RatingAverageTo { get; set; } + + [FromQuery(Name = "rating_count")] + public long? RatingCount { get; set; } + + [FromQuery(Name = "rating_count_from")] + public long? RatingCountFrom { get; set; } + + [FromQuery(Name = "rating_count_to")] + public long? RatingCountTo { get; set; } + + #endregion + + + + #region PUBLIC METHODS + + protected override bool IsMeetingConditions(PersonResponse item) => + ( + TestStringWithRegex(item.Name, Name) + && + TestStringWithRegex(item.FullName, FullName) + && + TestStringWithRegex(item.Description, Description) + && + TestComparable(item.BirthDate, BirthDate, BirthDateFrom, BirthDateTo) + && + TestComparable(item.DeathDate, DeathDate, DeathDateFrom, DeathDateTo) + && + Test(item.Gender?.Id, GenderId) + && + TestComparable(item.Rating.Average, RatingAverage, RatingAverageFrom, RatingAverageTo) + && + TestComparable(item.Rating.Count, RatingCount, RatingCountFrom, RatingCountTo) + ); + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Persons/PersonRequest.cs b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonRequest.cs new file mode 100644 index 0000000..5379e05 --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonRequest.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Serialization; + +namespace WatchIt.Common.Model.Persons; + +public class PersonRequest : Person +{ + #region PROPERTIES + + [JsonPropertyName("gender_id")] + public short? GenderId { get; set; } + + #endregion + + + + #region PUBLIC METHODS + + public Database.Model.Person.Person CreatePerson() => new Database.Model.Person.Person + { + Name = Name, + FullName = FullName, + Description = Description, + BirthDate = BirthDate, + DeathDate = DeathDate, + GenderId = GenderId, + }; + + public void UpdatePerson(Database.Model.Person.Person person) + { + person.Name = Name; + person.FullName = FullName; + person.Description = Description; + person.BirthDate = BirthDate; + person.DeathDate = DeathDate; + person.GenderId = GenderId; + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Persons/PersonResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonResponse.cs new file mode 100644 index 0000000..3ff33ee --- /dev/null +++ b/WatchIt.Common/WatchIt.Common.Model/Persons/PersonResponse.cs @@ -0,0 +1,57 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; +using WatchIt.Common.Model.Genders; +using WatchIt.Common.Model.Rating; +using WatchIt.Common.Query; + +namespace WatchIt.Common.Model.Persons; + +public class PersonResponse : Person, IQueryOrderable +{ + #region PROPERTIES + + [JsonIgnore] + public static IDictionary> OrderableProperties { get; } = new Dictionary> + { + { "id", x => x.Id }, + { "name", x => x.Name }, + { "full_name", x => x.FullName }, + { "description", x => x.Description }, + { "birth_date", x => x.BirthDate }, + { "death_date", x => x.BirthDate }, + { "gender", x => x.Gender.Name }, + { "rating.average", x => x.Rating.Average }, + { "rating.count", x => x.Rating.Count } + }; + + + [JsonPropertyName("id")] + public required long Id { get; set; } + + [JsonPropertyName("gender")] + public GenderResponse? Gender { get; set; } + + [JsonPropertyName("rating")] + public required RatingResponse Rating { get; set; } + + #endregion + + + + #region CONSTRUCTORS + + [SetsRequiredMembers] + public PersonResponse(Database.Model.Person.Person person) + { + Id = person.Id; + Name = person.Name; + FullName = person.FullName; + Description = person.Description; + BirthDate = person.BirthDate; + DeathDate = person.DeathDate; + Gender = person.Gender is not null ? new GenderResponse(person.Gender) : null; + Rating = RatingResponse.Create(person.PersonActorRoles, person.PersonCreatorRoles); + } + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Photos/PhotoQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Photos/PhotoQueryParameters.cs index b9d2794..33b5610 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Photos/PhotoQueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Photos/PhotoQueryParameters.cs @@ -32,9 +32,9 @@ public class PhotoQueryParameters : QueryParameters - #region PUBLIC METHODS + #region PRIVATE METHODS - public override bool IsMeetingConditions(PhotoResponse item) => + protected override bool IsMeetingConditions(PhotoResponse item) => ( TestStringWithRegex(item.MimeType, MimeType) && diff --git a/WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs index cc86fb2..6b34940 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Rating/RatingResponse.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; +using WatchIt.Database.Model.Person; using WatchIt.Database.Model.Rating; namespace WatchIt.Common.Model.Rating; @@ -24,14 +25,24 @@ public class RatingResponse public RatingResponse() {} [SetsRequiredMembers] - public RatingResponse(IEnumerable ratingMedia) : this(ratingMedia.Any() ? (decimal)ratingMedia.Average(x => x.Rating) : 0, ratingMedia.Count()) {} - - [SetsRequiredMembers] - public RatingResponse(decimal ratingAverage, long ratingCount) + private RatingResponse(long ratingSum, long ratingCount) { - Average = ratingAverage; + Average = ratingCount > 0 ? (decimal)ratingSum / ratingCount : 0; Count = ratingCount; } + public static RatingResponse Create(long ratingSum, long ratingCount) => new RatingResponse(ratingSum, ratingCount); + + public static RatingResponse Create(IEnumerable ratingMedia) => new RatingResponse(ratingMedia.Sum(x => x.Rating), ratingMedia.Count()); + + public static RatingResponse Create(IEnumerable personActorRoles, IEnumerable personCreatorRoles) + { + IEnumerable ratingsActorRoles = personActorRoles.SelectMany(x => x.RatingPersonActorRole); + IEnumerable ratingsCreatorRoles = personCreatorRoles.SelectMany(x => x.RatingPersonCreatorRole); + long ratingSum = ratingsActorRoles.Sum(x => x.Rating) + ratingsCreatorRoles.Sum(x => x.Rating); + long ratingCount = ratingsActorRoles.Count() + ratingsCreatorRoles.Count(); + return new RatingResponse(ratingSum, ratingCount); + } + #endregion } \ No newline at end of file diff --git a/WatchIt.Common/WatchIt.Common.Model/Series/SeriesQueryParameters.cs b/WatchIt.Common/WatchIt.Common.Model/Series/SeriesQueryParameters.cs index f4594c0..164a029 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Series/SeriesQueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Series/SeriesQueryParameters.cs @@ -59,9 +59,9 @@ public class SeriesQueryParameters : QueryParameters - #region PUBLIC METHODS + #region PRIVATE METHODS - public override bool IsMeetingConditions(SeriesResponse item) => + protected override bool IsMeetingConditions(SeriesResponse item) => ( TestStringWithRegex(item.Title, Title) && diff --git a/WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs b/WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs index 862d6e0..fe3fa4b 100644 --- a/WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs +++ b/WatchIt.Common/WatchIt.Common.Model/Series/SeriesResponse.cs @@ -50,7 +50,7 @@ public class SeriesResponse : Series, IQueryOrderable ReleaseDate = mediaSeries.Media.ReleaseDate; Length = mediaSeries.Media.Length; HasEnded = mediaSeries.HasEnded; - Rating = new RatingResponse(mediaSeries.Media.RatingMedia); + Rating = RatingResponse.Create(mediaSeries.Media.RatingMedia); } #endregion diff --git a/WatchIt.Common/WatchIt.Common.Query/QueryParameters.cs b/WatchIt.Common/WatchIt.Common.Query/QueryParameters.cs index d946e85..c574197 100644 --- a/WatchIt.Common/WatchIt.Common.Query/QueryParameters.cs +++ b/WatchIt.Common/WatchIt.Common.Query/QueryParameters.cs @@ -122,7 +122,7 @@ public abstract class QueryParameters : QueryParameters where T : IQueryOrder { #region PUBLIC METHODS - public abstract bool IsMeetingConditions(T item); + protected abstract bool IsMeetingConditions(T item); public IEnumerable PrepareData(IEnumerable data) { diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/PersonsController.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/PersonsController.cs new file mode 100644 index 0000000..aaf1d45 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/PersonsController.cs @@ -0,0 +1,84 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using WatchIt.Common.Model.Persons; +using WatchIt.WebAPI.Services.Controllers.Persons; + +namespace WatchIt.WebAPI.Controllers; + +[ApiController] +[Route("persons")] +public class PersonsController : ControllerBase +{ + #region SERVICES + + private readonly IPersonsControllerService _personsControllerService; + + #endregion + + + + #region CONSTRUCTORS + + public PersonsController(IPersonsControllerService personsControllerService) + { + _personsControllerService = personsControllerService; + } + + #endregion + + + + #region METHODS + + #region Main + + [HttpGet] + [AllowAnonymous] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllMovies(PersonQueryParameters query) => await _personsControllerService.GetAllPersons(query); + + [HttpGet("{id}")] + [AllowAnonymous] + [ProducesResponseType(typeof(PersonResponse), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMovie([FromRoute] long id) => await _personsControllerService.GetPerson(id); + + [HttpPost] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(PersonResponse), StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] + public async Task PostMovie([FromBody] PersonRequest body) => await _personsControllerService.PostPerson(body); + + [HttpPut("{id}")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] + public async Task PutMovie([FromRoute] long id, [FromBody]PersonRequest body) => await _personsControllerService.PutPerson(id, body); + + [HttpDelete("{id}")] + [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] + [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] + [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)] + public async Task DeleteMovie([FromRoute] long id) => await _personsControllerService.DeletePerson(id); + + #endregion + + #region View count + + [HttpGet("view")] + [AllowAnonymous] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task GetMoviesViewRank([FromQuery] int first = 5, [FromQuery] int days = 7) => await _personsControllerService.GetPersonsViewRank(first, days); + + #endregion + + #endregion +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/WatchIt.WebAPI.Controllers.csproj b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/WatchIt.WebAPI.Controllers.csproj index 809068e..b254b7e 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/WatchIt.WebAPI.Controllers.csproj +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Controllers/WatchIt.WebAPI.Controllers.csproj @@ -17,6 +17,7 @@ + diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs index 295d923..f8a3f2b 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Media/MediaControllerService.cs @@ -108,7 +108,7 @@ public class MediaControllerService(DatabaseContext database, IUserService userS return RequestResult.NotFound(); } - RatingResponse ratingResponse = new RatingResponse(item.RatingMedia); + RatingResponse ratingResponse = RatingResponse.Create(item.RatingMedia); return RequestResult.Ok(ratingResponse); } diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/IPersonsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/IPersonsControllerService.cs new file mode 100644 index 0000000..f9b5f6f --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/IPersonsControllerService.cs @@ -0,0 +1,14 @@ +using WatchIt.Common.Model.Persons; +using WatchIt.WebAPI.Services.Controllers.Common; + +namespace WatchIt.WebAPI.Services.Controllers.Persons; + +public interface IPersonsControllerService +{ + Task GetAllPersons(PersonQueryParameters query); + Task GetPerson(long id); + Task PostPerson(PersonRequest data); + Task PutPerson(long id, PersonRequest data); + Task DeletePerson(long id); + Task GetPersonsViewRank(int first, int days); +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/PersonsControllerService.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/PersonsControllerService.cs new file mode 100644 index 0000000..09137fe --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/PersonsControllerService.cs @@ -0,0 +1,146 @@ +using Microsoft.EntityFrameworkCore; +using WatchIt.Common.Model.Persons; +using WatchIt.Database; +using WatchIt.WebAPI.Services.Controllers.Common; +using WatchIt.WebAPI.Services.Utility.User; +using Person = WatchIt.Database.Model.Person.Person; + +namespace WatchIt.WebAPI.Services.Controllers.Persons; + +public class PersonsControllerService : IPersonsControllerService +{ + #region SERVICES + + private readonly DatabaseContext _database; + private readonly IUserService _userService; + + #endregion + + + + #region CONSTRUCTORS + + public PersonsControllerService(DatabaseContext database, IUserService userService) + { + _database = database; + _userService = userService; + } + + #endregion + + + + #region PUBLIC METHODS + + #region Main + + public async Task GetAllPersons(PersonQueryParameters query) + { + IEnumerable rawData = await _database.Persons.ToListAsync(); + IEnumerable data = rawData.Select(x => new PersonResponse(x)); + data = query.PrepareData(data); + return RequestResult.Ok(data); + } + + public async Task GetPerson(long id) + { + Person? item = await _database.Persons.FirstOrDefaultAsync(x => x.Id == id); + if (item is null) + { + return RequestResult.NotFound(); + } + + PersonResponse data = new PersonResponse(item); + return RequestResult.Ok(data); + } + + public async Task PostPerson(PersonRequest data) + { + UserValidator validator = _userService.GetValidator().MustBeAdmin(); + if (!validator.IsValid) + { + return RequestResult.Forbidden(); + } + + Person personItem = data.CreatePerson(); + await _database.Persons.AddAsync(personItem); + await _database.SaveChangesAsync(); + + return RequestResult.Created($"persons/{personItem.Id}", new PersonResponse(personItem)); + } + + public async Task PutPerson(long id, PersonRequest data) + { + UserValidator validator = _userService.GetValidator().MustBeAdmin(); + if (!validator.IsValid) + { + return RequestResult.Forbidden(); + } + + Person? item = await _database.Persons.FirstOrDefaultAsync(x => x.Id == id); + if (item is null) + { + return RequestResult.NotFound(); + } + + data.UpdatePerson(item); + + await _database.SaveChangesAsync(); + + return RequestResult.Ok(); + } + + public async Task DeletePerson(long id) + { + UserValidator validator = _userService.GetValidator().MustBeAdmin(); + if (!validator.IsValid) + { + return RequestResult.Forbidden(); + } + + Person? item = await _database.Persons.FirstOrDefaultAsync(x => x.Id == id); + if (item is null) + { + return RequestResult.NotFound(); + } + + _database.PersonCreatorRoles.AttachRange(item.PersonCreatorRoles); + _database.PersonCreatorRoles.RemoveRange(item.PersonCreatorRoles); + _database.PersonActorRoles.AttachRange(item.PersonActorRoles); + _database.PersonActorRoles.RemoveRange(item.PersonActorRoles); + _database.ViewCountsPerson.AttachRange(item.ViewCountsPerson); + _database.ViewCountsPerson.RemoveRange(item.ViewCountsPerson); + _database.Persons.Attach(item); + _database.Persons.Remove(item); + await _database.SaveChangesAsync(); + + return RequestResult.NoContent(); + } + + #endregion + + #region View count + + public async Task GetPersonsViewRank(int first, int days) + { + if (first < 1 || days < 1) + { + return RequestResult.BadRequest(); + } + + DateOnly startDate = DateOnly.FromDateTime(DateTime.Now).AddDays(-days); + IEnumerable rawData = await _database.Persons.OrderByDescending(x => x.ViewCountsPerson.Where(y => y.Date >= startDate) + .Sum(y => y.ViewCount)) + .ThenBy(x => x.Id) + .Take(first) + .ToListAsync(); + + IEnumerable data = rawData.Select(x => new PersonResponse(x)); + + return RequestResult.Ok(data); + } + + #endregion + + #endregion +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/WatchIt.WebAPI.Services.Controllers.Persons.csproj b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/WatchIt.WebAPI.Services.Controllers.Persons.csproj new file mode 100644 index 0000000..9e4603e --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Services/WatchIt.WebAPI.Services.Controllers/WatchIt.WebAPI.Services.Controllers.Persons/WatchIt.WebAPI.Services.Controllers.Persons.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + + + + + + + + + + diff --git a/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Persons/PersonRequestValidator.cs b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Persons/PersonRequestValidator.cs new file mode 100644 index 0000000..f244081 --- /dev/null +++ b/WatchIt.WebAPI/WatchIt.WebAPI.Validators/Persons/PersonRequestValidator.cs @@ -0,0 +1,9 @@ +using FluentValidation; +using WatchIt.Common.Model.Persons; + +namespace WatchIt.WebAPI.Validators.Persons; + +public class PersonRequestValidator : AbstractValidator +{ + +} \ No newline at end of file diff --git a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs index 7bd902b..4e2c243 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs +++ b/WatchIt.WebAPI/WatchIt.WebAPI/Program.cs @@ -13,6 +13,7 @@ using WatchIt.WebAPI.Services.Controllers.Accounts; using WatchIt.WebAPI.Services.Controllers.Genres; using WatchIt.WebAPI.Services.Controllers.Media; using WatchIt.WebAPI.Services.Controllers.Movies; +using WatchIt.WebAPI.Services.Controllers.Persons; using WatchIt.WebAPI.Services.Controllers.Photos; using WatchIt.WebAPI.Services.Controllers.Series; using WatchIt.WebAPI.Services.Utility.Configuration; @@ -158,6 +159,7 @@ public static class Program builder.Services.AddTransient(); builder.Services.AddTransient(); builder.Services.AddTransient(); + builder.Services.AddTransient(); return builder; } diff --git a/WatchIt.WebAPI/WatchIt.WebAPI/WatchIt.WebAPI.csproj b/WatchIt.WebAPI/WatchIt.WebAPI/WatchIt.WebAPI.csproj index 5c06b61..37917aa 100644 --- a/WatchIt.WebAPI/WatchIt.WebAPI/WatchIt.WebAPI.csproj +++ b/WatchIt.WebAPI/WatchIt.WebAPI/WatchIt.WebAPI.csproj @@ -27,6 +27,7 @@ + diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Endpoints.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Endpoints.cs index 765dd46..ef20f7a 100644 --- a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Endpoints.cs +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Endpoints.cs @@ -9,4 +9,5 @@ public class Endpoints public Movies Movies { get; set; } public Series Series { get; set; } public Photos Photos { get; set; } + public Persons Persons { get; set; } } \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Persons.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Persons.cs new file mode 100644 index 0000000..1bf760a --- /dev/null +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.Utility/WatchIt.Website.Services.Utility.Configuration/Model/Persons.cs @@ -0,0 +1,12 @@ +namespace WatchIt.Website.Services.Utility.Configuration.Model; + +public class Persons +{ + public string Base { get; set; } + public string GetAllPersons { get; set; } + public string GetPerson { get; set; } + public string PostPerson { get; set; } + public string PutPerson { get; set; } + public string DeletePerson { get; set; } + public string GetPersonsViewRank { get; set; } +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/IPersonsWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/IPersonsWebAPIService.cs new file mode 100644 index 0000000..2f2181f --- /dev/null +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/IPersonsWebAPIService.cs @@ -0,0 +1,13 @@ +using WatchIt.Common.Model.Persons; + +namespace WatchIt.Website.Services.WebAPI.Persons; + +public interface IPersonsWebAPIService +{ + Task GetAllPersons(PersonQueryParameters? query = null, Action>? successAction = null); + Task GetPerson(long id, Action? successAction = null, Action? notFoundAction = null); + Task PostPerson(PersonRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); + Task PutPerson(long id, PersonRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); + Task DeletePerson(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null); + Task GetPersonsViewRank(int? first = null, int? days = null, Action>? successAction = null, Action>? badRequestAction = null); +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/PersonsWebAPIService.cs b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/PersonsWebAPIService.cs new file mode 100644 index 0000000..b19d450 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/PersonsWebAPIService.cs @@ -0,0 +1,152 @@ +using System.Text; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Primitives; +using WatchIt.Common.Model.Persons; +using WatchIt.Common.Services.HttpClient; +using WatchIt.Website.Services.Utility.Configuration; +using WatchIt.Website.Services.WebAPI.Common; + +namespace WatchIt.Website.Services.WebAPI.Persons; + +public class PersonsWebAPIService : BaseWebAPIService, IPersonsWebAPIService +{ + #region SERVICES + + private IHttpClientService _httpClientService; + private IConfigurationService _configurationService; + + #endregion + + + + #region CONSTRUCTORS + + public PersonsWebAPIService(IHttpClientService httpClientService, IConfigurationService configurationService) : base(configurationService) + { + _httpClientService = httpClientService; + _configurationService = configurationService; + } + + #endregion + + + + #region PUBLIC METHODS + + #region Main + + public async Task GetAllPersons(PersonQueryParameters? query = null, Action>? successAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.GetAllPersons); + + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + request.Query = query; + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .ExecuteAction(); + } + + public async Task GetPerson(long id, Action? successAction = null, Action? notFoundAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.GetPerson, id); + + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor404NotFound(notFoundAction) + .ExecuteAction(); + } + + public async Task PostPerson(PersonRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.PostPerson); + + HttpRequest request = new HttpRequest(HttpMethodType.Post, url); + request.Body = data; + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor403Forbidden(forbiddenAction) + .ExecuteAction(); + } + + public async Task PutPerson(long id, PersonRequest data, Action? successAction = null, Action>? badRequestAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.PutPerson, id); + + HttpRequest request = new HttpRequest(HttpMethodType.Put, url); + request.Body = data; + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor403Forbidden(forbiddenAction) + .ExecuteAction(); + } + + public async Task DeletePerson(long id, Action? successAction = null, Action? unauthorizedAction = null, Action? forbiddenAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.DeletePerson, id); + + HttpRequest request = new HttpRequest(HttpMethodType.Delete, url); + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor401Unauthorized(unauthorizedAction) + .RegisterActionFor403Forbidden(forbiddenAction) + .ExecuteAction(); + } + + #endregion + + #region View count + + public async Task GetPersonsViewRank(int? first = null, int? days = null, Action>? successAction = null, Action>? badRequestAction = null) + { + string url = GetUrl(EndpointsConfiguration.Persons.GetPersonsViewRank); + if (first.HasValue || days.HasValue) + { + StringBuilder urlBuilder = new StringBuilder(url); + urlBuilder.Append('?'); + bool firstParameter = true; + if (first.HasValue) + { + urlBuilder.Append($"first={first.Value}"); + firstParameter = false; + } + if (days.HasValue) + { + if (!firstParameter) + { + urlBuilder.Append('&'); + } + urlBuilder.Append($"days={days.Value}"); + } + url = urlBuilder.ToString(); + } + + HttpRequest request = new HttpRequest(HttpMethodType.Get, url); + + HttpResponse response = await _httpClientService.SendRequestAsync(request); + response.RegisterActionFor2XXSuccess(successAction) + .RegisterActionFor400BadRequest(badRequestAction) + .ExecuteAction(); + } + + #endregion + + #endregion + + + + #region PRIVATE METHODS + + protected override string GetServiceBase() => EndpointsConfiguration.Persons.Base; + + #endregion +} \ No newline at end of file diff --git a/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/WatchIt.Website.Services.WebAPI.Persons.csproj b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/WatchIt.Website.Services.WebAPI.Persons.csproj new file mode 100644 index 0000000..abf3359 --- /dev/null +++ b/WatchIt.Website/WatchIt.Website.Services/WatchIt.Website.Services.WebAPI/WatchIt.Website.Services.WebAPI.Persons/WatchIt.Website.Services.WebAPI.Persons.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/WatchIt.Website/WatchIt.Website/appsettings.json b/WatchIt.Website/WatchIt.Website/appsettings.json index 02e7c38..ab714ae 100644 --- a/WatchIt.Website/WatchIt.Website/appsettings.json +++ b/WatchIt.Website/WatchIt.Website/appsettings.json @@ -70,6 +70,15 @@ "DeletePhoto": "/{0}", "PutPhotoBackgroundData": "/{0}/background_data", "DeletePhotoBackgroundData": "/{0}/background_data" + }, + "Persons": { + "Base": "/persons", + "GetAllPersons": "", + "GetPerson": "/{0}", + "PostPerson": "", + "PutPerson": "/{0}", + "DeletePerson": "/{0}", + "GetPersonsViewRank": "/view" } } } diff --git a/WatchIt.sln b/WatchIt.sln index 07a0606..92de90e 100644 --- a/WatchIt.sln +++ b/WatchIt.sln @@ -88,6 +88,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Con EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Photos", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Photos\WatchIt.Website.Services.WebAPI.Photos.csproj", "{960A833F-C195-4D1D-AD4F-D00B57067181}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.WebAPI.Services.Controllers.Persons", "WatchIt.WebAPI\WatchIt.WebAPI.Services\WatchIt.WebAPI.Services.Controllers\WatchIt.WebAPI.Services.Controllers.Persons\WatchIt.WebAPI.Services.Controllers.Persons.csproj", "{335686F5-65B8-4D66-AAA8-C5032906451D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchIt.Website.Services.WebAPI.Persons", "WatchIt.Website\WatchIt.Website.Services\WatchIt.Website.Services.WebAPI\WatchIt.Website.Services.WebAPI.Persons\WatchIt.Website.Services.WebAPI.Persons.csproj", "{83D42D72-FF67-4577-8280-2ABD5B20F985}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -134,6 +138,8 @@ Global {783C743A-85BF-4382-BFE5-7A90E3F3B8B6} = {46E3711F-18BD-4004-AF53-EA4D8643D92F} {ABDF8471-2FAB-4930-B016-7DD3E48AE6B8} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7} {960A833F-C195-4D1D-AD4F-D00B57067181} = {46E3711F-18BD-4004-AF53-EA4D8643D92F} + {335686F5-65B8-4D66-AAA8-C5032906451D} = {CEC468DB-CC49-47D3-9E3E-1CC9530C3CE7} + {83D42D72-FF67-4577-8280-2ABD5B20F985} = {46E3711F-18BD-4004-AF53-EA4D8643D92F} EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {23383776-1F27-4B5D-8C7C-57BFF75FA473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -264,5 +270,13 @@ Global {960A833F-C195-4D1D-AD4F-D00B57067181}.Debug|Any CPU.Build.0 = Debug|Any CPU {960A833F-C195-4D1D-AD4F-D00B57067181}.Release|Any CPU.ActiveCfg = Release|Any CPU {960A833F-C195-4D1D-AD4F-D00B57067181}.Release|Any CPU.Build.0 = Release|Any CPU + {335686F5-65B8-4D66-AAA8-C5032906451D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {335686F5-65B8-4D66-AAA8-C5032906451D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {335686F5-65B8-4D66-AAA8-C5032906451D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {335686F5-65B8-4D66-AAA8-C5032906451D}.Release|Any CPU.Build.0 = Release|Any CPU + {83D42D72-FF67-4577-8280-2ABD5B20F985}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83D42D72-FF67-4577-8280-2ABD5B20F985}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83D42D72-FF67-4577-8280-2ABD5B20F985}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83D42D72-FF67-4577-8280-2ABD5B20F985}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal