From 62bf4534b5655a054189cfa6e6d97329c245fb14 Mon Sep 17 00:00:00 2001 From: NICOLAS MALULEKE Date: Thu, 19 Nov 2020 13:39:56 +0200 Subject: [PATCH 01/11] Created Review entity and CRUD apis --- .../Common/Exceptions/NotFoundException.cs | 19 +- .../Common/Exceptions/PostException.cs | 32 + .../Interfaces/IApplicationDbContext.cs | 3 + src/Application/Common/Models/StatusCode.cs | 12 + .../CreateIssueTicketCommand.cs | 33 +- .../CreateIssueTicketCommandHandler.cs | 34 - .../CreateIssueTicketCommandValidator.cs | 8 +- .../UpdateIssueTicketCommandValidator.cs | 8 +- .../GetIssueTicketDetailQuery.cs | 21 +- .../GetIssueDetail/IssueTicketDetailVm.cs | 33 + .../Queries/GetIssueList/IssueTicketDto.cs | 2 + .../CreateReview/CreateReviewCommand.cs | 49 ++ .../CreateReviewCommandValidator.cs | 23 + .../DeleteReview/DeleteReviewCommand.cs | 38 ++ .../DeleteReviewCommandValidator.cs | 37 ++ .../Commands/ReviewCommandBaseHandler.cs | 21 + .../CreateReviewCommandValidator.cs | 25 + .../UpdateReview/UpdateReviewCommand.cs | 42 ++ .../GetReviewDetails/GetReviewDetailsQuery.cs | 46 ++ .../Query/GetReviewList/GetReviewListQuery.cs | 60 ++ .../Reviews/Query/GetReviewList/ReviewDto.cs | 22 + .../Query/GetReviewList/ReviewListVm.cs | 9 + src/Domain/Common/AuditableEntity.cs | 1 + src/Domain/Entities/Category.cs | 1 - src/Domain/Entities/IssueTicket.cs | 6 +- src/Domain/Entities/Review.cs | 19 + .../Persistence/ApplicationDbContext.cs | 1 + .../Persistence/ApplicationDbContextSeed.cs | 56 +- .../Configurations/CategoryConfiguration.cs | 5 + .../IssueTicketConfiguration.cs | 8 + .../Configurations/ReviewConfiguration.cs | 29 + .../20201116080757_EmptyMigration.cs | 17 - ...01118041232_CreateReviewTable.Designer.cs} | 50 +- .../20201118041232_CreateReviewTable.cs | 71 ++ .../ApplicationDbContextModelSnapshot.cs | 46 ++ src/WebUI/ClientApp/angular.json | 6 +- src/WebUI/ClientApp/package-lock.json | 17 + src/WebUI/ClientApp/package.json | 1 + src/WebUI/ClientApp/src/app/CodeClinic-api.ts | 614 ++++++++++++++++-- src/WebUI/ClientApp/src/app/app.module.ts | 4 +- .../src/app/home/home.component.html | 67 +- .../ClientApp/src/app/home/home.component.ts | 15 +- .../src/pipes/summary/summary.pipe.spec.ts | 8 + .../src/pipes/summary/summary.pipe.ts | 18 + src/WebUI/Controllers/CategoriesController.cs | 7 +- .../Controllers/IssueTicketsController.cs | 6 +- src/WebUI/Controllers/ReviewsController.cs | 52 ++ src/WebUI/wwwroot/api/specification.json | 389 +++++++++-- .../Common/Mappings/MappingTests.cs | 2 + 49 files changed, 1856 insertions(+), 237 deletions(-) create mode 100644 src/Application/Common/Exceptions/PostException.cs create mode 100644 src/Application/Common/Models/StatusCode.cs delete mode 100644 src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandHandler.cs create mode 100644 src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs create mode 100644 src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs create mode 100644 src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs create mode 100644 src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs create mode 100644 src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs create mode 100644 src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs create mode 100644 src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs create mode 100644 src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs create mode 100644 src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs create mode 100644 src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs create mode 100644 src/Application/Reviews/Query/GetReviewList/ReviewDto.cs create mode 100644 src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs create mode 100644 src/Domain/Entities/Review.cs create mode 100644 src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs delete mode 100644 src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.cs rename src/Infrastructure/Persistence/Migrations/{20201116080757_EmptyMigration.Designer.cs => 20201118041232_CreateReviewTable.Designer.cs} (89%) create mode 100644 src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.cs create mode 100644 src/WebUI/ClientApp/src/pipes/summary/summary.pipe.spec.ts create mode 100644 src/WebUI/ClientApp/src/pipes/summary/summary.pipe.ts create mode 100644 src/WebUI/Controllers/ReviewsController.cs diff --git a/src/Application/Common/Exceptions/NotFoundException.cs b/src/Application/Common/Exceptions/NotFoundException.cs index 9718df9..696bea8 100644 --- a/src/Application/Common/Exceptions/NotFoundException.cs +++ b/src/Application/Common/Exceptions/NotFoundException.cs @@ -2,25 +2,30 @@ namespace CodeClinic.Application.Common.Exceptions { - public class NotFoundException : Exception + public class PostException : Exception { - public NotFoundException() + public PostException() : base() { + + } - public NotFoundException(string message) + public PostException(string message) : base(message) { + + } - public NotFoundException(string message, Exception innerException) - : base(message, innerException) + + public PostException(string name, Exception innerException) + : base(name?? $"Failed to create entity \"{name}\" " , innerException) { } - public NotFoundException(string name, object key) - : base($"Entity \"{name}\" ({key}) was not found.") + public PostException(string name, string reason) + : base($"Failed to create entity \"{name}\" because {reason}") { } } diff --git a/src/Application/Common/Exceptions/PostException.cs b/src/Application/Common/Exceptions/PostException.cs new file mode 100644 index 0000000..7ffba5a --- /dev/null +++ b/src/Application/Common/Exceptions/PostException.cs @@ -0,0 +1,32 @@ +using System; + +namespace CodeClinic.Application.Common.Exceptions +{ + public class NotFoundException : Exception + { + public NotFoundException() + : base() + { + + + } + + public NotFoundException(string message) + : base(message) + { + + + } + + + public NotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + public NotFoundException(string name, object key) + : base($"Entity \"{name}\" ({key}) was not found.") + { + } + } +} diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs index 01c312c..655dea1 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -10,6 +10,9 @@ public interface IApplicationDbContext DbSet IssueTickets {get;set;} DbSet Categories { get; set; } + DbSet Reviews { get; set; } + + Task SaveChangesAsync(CancellationToken cancellationToken); } } diff --git a/src/Application/Common/Models/StatusCode.cs b/src/Application/Common/Models/StatusCode.cs new file mode 100644 index 0000000..16222d4 --- /dev/null +++ b/src/Application/Common/Models/StatusCode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.Common.Models +{ + public static class ErrorCode + { + public const string BadRequest = "400"; + public const string NotFound = "404"; + } +} diff --git a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommand.cs b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommand.cs index 5c7966a..5989c32 100644 --- a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommand.cs +++ b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommand.cs @@ -1,5 +1,8 @@ +using CodeClinic.Application.Common.Interfaces; using CodeClinic.Domain.Entities; using MediatR; +using System.Threading; +using System.Threading.Tasks; namespace Application.Issues.Commands.CreateIssue { @@ -11,7 +14,35 @@ public class CreateIssueTicketCommand : IRequest public int CategoryId { get; set; } public string Body { get; set; } - + + public class CreateIssueTicketCommandHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + public CreateIssueTicketCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + + public async Task Handle(CreateIssueTicketCommand request, CancellationToken cancellationToken) + { + var entity = new IssueTicket + { + Title = request.Title, + CategoryId = request.CategoryId, + Body = request.Body, + }; + + _context.IssueTickets.Add(entity); + + + await _context.SaveChangesAsync(cancellationToken); + return entity.Id; + } + + + } + } diff --git a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandHandler.cs b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandHandler.cs deleted file mode 100644 index 7c590c3..0000000 --- a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -using CodeClinic.Application.Common.Interfaces; -using CodeClinic.Domain.Entities; -using MediatR; -using System.Threading; -using System.Threading.Tasks; - -namespace Application.Issues.Commands.CreateIssue -{ - public class CreateIssueTicketCommandHandler : IRequestHandler - { - private readonly IApplicationDbContext _context; - public CreateIssueTicketCommandHandler(IApplicationDbContext context) - { - _context = context; - } - public async Task Handle(CreateIssueTicketCommand request, CancellationToken cancellationToken) - { - var entity = new IssueTicket - { - Title = request.Title, - CategoryId = request.CategoryId, - Body = request.Body, - - }; - - _context.IssueTickets.Add(entity); - - await _context.SaveChangesAsync(cancellationToken); - return entity.Id; - } - } - - -} \ No newline at end of file diff --git a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs index de6107c..9679e71 100644 --- a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs +++ b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs @@ -16,9 +16,13 @@ public CreateIssueTicketCommandValidator(IApplicationDbContext context) RuleFor(v => v.Title) .MaximumLength(200) - .NotEmpty().WithMessage("Title cannot be longer than 200 characters"); + .NotEmpty() + .WithMessage("Title cannot be longer than 200 characters"); - RuleFor(c => c.CategoryId).NotNull().GreaterThan(0).MustAsync(HaveAnExistingCategory) + RuleFor(c => c.CategoryId) + .NotNull() + .GreaterThan(0) + .MustAsync(HaveAnExistingCategory) .WithMessage("Choose a Category that exists in the system"); _context = context; } diff --git a/src/Application/IssueTickets/Commands/UpdateIssueTicket/UpdateIssueTicketCommandValidator.cs b/src/Application/IssueTickets/Commands/UpdateIssueTicket/UpdateIssueTicketCommandValidator.cs index ef0bcb2..64d5c13 100644 --- a/src/Application/IssueTickets/Commands/UpdateIssueTicket/UpdateIssueTicketCommandValidator.cs +++ b/src/Application/IssueTickets/Commands/UpdateIssueTicket/UpdateIssueTicketCommandValidator.cs @@ -1,4 +1,5 @@ using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Application.Common.Models; using FluentValidation; using Microsoft.EntityFrameworkCore; using System.Threading; @@ -17,14 +18,15 @@ public class UpdateIssueTicketCommandValidator : AbstractValidator s.Stars) - .GreaterThanOrEqualTo(0).NotNull().NotEmpty() + .GreaterThanOrEqualTo(0).NotNull().NotEmpty().WithErrorCode(ErrorCode.BadRequest) .WithMessage("Star Rating Cannot be less than Zero"); RuleFor(a => a.Status) - .NotNull().NotEmpty() + .NotNull().NotEmpty().WithErrorCode(ErrorCode.BadRequest) .WithMessage("Status cannot be null or empty"); - RuleFor(c => c.CategoryId).NotNull().GreaterThan(0).MustAsync(HaveAnExistingCategory) + RuleFor(c => c.CategoryId).NotNull().GreaterThan(0) + .MustAsync(HaveAnExistingCategory).WithErrorCode(ErrorCode.NotFound) .WithMessage("Choose a Category that exists in the system"); _context = context; } diff --git a/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs b/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs index d62f494..fe172e7 100644 --- a/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs +++ b/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs @@ -3,22 +3,23 @@ using CodeClinic.Application.Common.Exceptions; using CodeClinic.Application.Common.Interfaces; using CodeClinic.Application.Issues.Queries.GetIssueList; +using CodeClinic.Application.IssueTickets.Queries.GetIssueDetail; +using CodeClinic.Application.Reviews.Query.GetReviewList; using CodeClinic.Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; +using System.Linq; using System.Threading; using System.Threading.Tasks; namespace CodeClinic.Application.IssueItems.Queries.GetIssueDetail { - public class GetIssueTicketDetailQuery : IRequest + public class GetIssueTicketDetailQuery : IRequest< IssueTicketDetailVm> { - public int Id { get; set; } - } - public class GetIssueDetailQueryHandler : IRequestHandler + public class GetIssueDetailQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; @@ -28,14 +29,20 @@ public GetIssueDetailQueryHandler(IApplicationDbContext context, IMapper mapper) _context = context; _mapper = mapper; } - public async Task Handle(GetIssueTicketDetailQuery request, CancellationToken cancellationToken) + public async Task Handle(GetIssueTicketDetailQuery request, + CancellationToken cancellationToken) { var viewModel = await _context.IssueTickets - .ProjectTo(_mapper.ConfigurationProvider) + .ProjectTo(_mapper.ConfigurationProvider) .FirstOrDefaultAsync(i => i.IssueTicketId == request.Id, cancellationToken); - + if(viewModel == null) throw new NotFoundException(nameof(IssueTicket), request.Id); + + var reviews = await _context.Reviews + .ProjectTo(_mapper.ConfigurationProvider) + .Where(x => x.IssueTicketId == request.Id).ToListAsync(cancellationToken); + viewModel.Reviews = reviews; return viewModel; } } diff --git a/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs b/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs new file mode 100644 index 0000000..99e3763 --- /dev/null +++ b/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs @@ -0,0 +1,33 @@ +using AutoMapper; +using CodeClinic.Application.Common.Mappings; +using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Domain.Entities; +using CodeClinic.Domain.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.IssueTickets.Queries.GetIssueDetail +{ + public class IssueTicketDetailVm:IMapFrom + { + public int CategoryId { get; set; } + public int IssueTicketId { get; set; } + public string Title { get; set; } + public int Stars { get; set; } + public string Body { get; set; } + + public ProgressStatus Status { get; set; } + public string CategoryName { get; set; } + + public IList Reviews { get; set; } = new List(); + + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(c => c.CategoryName, o => o.MapFrom(c => c.Category.Name)) + .ForMember(i => i.IssueTicketId, o => o.MapFrom(c => c.Id)); + } + } +} diff --git a/src/Application/IssueTickets/Queries/GetIssueList/IssueTicketDto.cs b/src/Application/IssueTickets/Queries/GetIssueList/IssueTicketDto.cs index b408f45..9c93116 100644 --- a/src/Application/IssueTickets/Queries/GetIssueList/IssueTicketDto.cs +++ b/src/Application/IssueTickets/Queries/GetIssueList/IssueTicketDto.cs @@ -23,6 +23,8 @@ public class IssueTicketDto :IMapFrom public string LastModifiedBy { get; set; } public DateTime? LastModified { get; set; } + + public void Mapping(Profile profile) { profile.CreateMap() diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs new file mode 100644 index 0000000..a931753 --- /dev/null +++ b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs @@ -0,0 +1,49 @@ +using CodeClinic.Application.Common.Exceptions; +using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Domain.Entities; +using MediatR; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Commands.CreateReview +{ + public class CreateReviewCommand : IRequest + { + public int IssueTicketId { get; set; } + public string Title { get; set; } + public string Description { get; set; } + + } + + + public class CreateReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + { + public CreateReviewCommandHandler(IApplicationDbContext context) : base(context) { } + + public async Task Handle(CreateReviewCommand request, CancellationToken cancellationToken) + { + var issueTicket = await _context.IssueTickets.FindAsync(request.IssueTicketId); + + if (issueTicket == null) + throw + new PostException(nameof(Review), + new NotFoundException($"The Depend Entity '{nameof(IssueTicket)}'" + + $" with Id {request.IssueTicketId} was not found")); + + var entity = new Review + { + IssueTicketId = request.IssueTicketId, + Title = request.Title, + Description = request.Description + }; + + _context.Reviews.Add(entity); + + await _context.SaveChangesAsync(cancellationToken); + return entity.Id; + } + } +} diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs new file mode 100644 index 0000000..c5c3814 --- /dev/null +++ b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs @@ -0,0 +1,23 @@ +using CodeClinic.Application.Common.Models; +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.Reviews.Commands.CreateReview +{ + class CreateReviewCommandValidator: AbstractValidator + { + public CreateReviewCommandValidator() + { + RuleFor(t => t.IssueTicketId) + .NotEmpty().NotNull() + .WithErrorCode( ErrorCode.BadRequest) + .WithMessage("Issue Ticket Id is Required"); + + RuleFor(t => t.Title) + .NotEmpty().WithErrorCode(ErrorCode.BadRequest) + .WithMessage("The title is required"); + } + } +} diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs new file mode 100644 index 0000000..cd4fec8 --- /dev/null +++ b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs @@ -0,0 +1,38 @@ +using CodeClinic.Application.Common.Exceptions; +using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Domain.Entities; +using MediatR; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Commands.DeleteReview +{ + public class DeleteReviewCommand : IRequest + { + public int IssueTicketId { get; set; } + + public int Id { get; set; } + } + + public class DeleteReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + { + public DeleteReviewCommandHandler(IApplicationDbContext context) : base(context) { } + + + public async Task Handle(DeleteReviewCommand request, CancellationToken cancellationToken) + { + var entity = await _context.Reviews.FindAsync(request.Id); + + if (entity == null) throw new NotFoundException(nameof(Review), request.Id); + + _context.Reviews.Remove(entity); + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } + } +} diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs new file mode 100644 index 0000000..6b50da2 --- /dev/null +++ b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs @@ -0,0 +1,37 @@ +using CodeClinic.Application.Common.Interfaces; +using FluentValidation; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Commands.DeleteReview +{ + class DeleteReviewCommandValidator :AbstractValidator + { + private readonly IApplicationDbContext _context; + + public DeleteReviewCommandValidator(IApplicationDbContext context) + { + _context = context; + } + public DeleteReviewCommandValidator() + { + RuleFor(i => i.Id) + .NotEmpty().NotNull().WithMessage("Review Id is Required!"); + + RuleFor(i => i.IssueTicketId).NotEmpty() + .MustAsync(MustBeInTheIssueTicket) + .WithMessage("Ticket does not contain review with given Id!"); + } + + public async Task MustBeInTheIssueTicket(DeleteReviewCommand request, int issueTickeId, CancellationToken cancellationToken) + { + var ticket = await _context.IssueTickets.FirstOrDefaultAsync(i => i.Id == request.IssueTicketId && i.Reviews.FirstOrDefault(r => r.IssueTicketId == issueTickeId ) != null); + return ticket != null; + } + } +} diff --git a/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs b/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs new file mode 100644 index 0000000..d6b7ff3 --- /dev/null +++ b/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs @@ -0,0 +1,21 @@ +using CodeClinic.Application.Common.Interfaces; +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.Reviews.Commands +{ + public class ReviewCommandBaseHandler + { + protected readonly IApplicationDbContext _context; + + public ReviewCommandBaseHandler(IApplicationDbContext context) + { + _context = context; + } + + + } + +} diff --git a/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs b/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs new file mode 100644 index 0000000..e4cf65a --- /dev/null +++ b/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.Reviews.Commands.UpdateReview +{ + class UpdateReviewCommandValidator : AbstractValidator + { + public UpdateReviewCommandValidator() + { + RuleFor(i => i.ReviewId).NotEmpty() + .NotNull(); + + RuleFor(t => t.IssueTicketId) + .NotEmpty().NotNull() + .WithErrorCode("400") + .WithMessage("Issue Ticket Id is Required"); + + RuleFor(t => t.Title) + .NotEmpty() + .WithMessage("The title is required"); + } + } +} diff --git a/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs b/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs new file mode 100644 index 0000000..24570c7 --- /dev/null +++ b/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs @@ -0,0 +1,42 @@ +using CodeClinic.Application.Common.Exceptions; +using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Domain.Entities; +using FluentValidation; +using MediatR; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Commands.UpdateReview +{ + public class UpdateReviewCommand : IRequest + { + public int IssueTicketId { get; set; } + public int ReviewId { get; set; } + public string Title { get; set; } + public string Description { get; set; } + } + public class UpdateReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + { + public UpdateReviewCommandHandler(IApplicationDbContext context) : base(context) { } + + public async Task Handle(UpdateReviewCommand request, CancellationToken cancellationToken) + { + + Review entity = await _context.Reviews.FindAsync(request.ReviewId); + + if (entity == null) throw new NotFoundException(nameof(Review), request.ReviewId); + + entity.Title = request.Title; + entity.Description = request.Description; + + _context.Reviews.Update(entity); + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } + + } +} diff --git a/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs b/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs new file mode 100644 index 0000000..2d283ba --- /dev/null +++ b/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs @@ -0,0 +1,46 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using CodeClinic.Application.Common.Exceptions; +using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Query.GetReviewDetails +{ + public class GetReviewDetailsQuery : IRequest + { + public int IssueTicketId { get; set; } + public int ReviewId { get; set; } + } + + public class GetReviewDetailsQueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public GetReviewDetailsQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + public async Task Handle(GetReviewDetailsQuery request, CancellationToken cancellationToken) + { + var diagnosis = await _context.Reviews + .ProjectTo(_mapper.ConfigurationProvider) + .SingleOrDefaultAsync(i => i.ReviewId == request.ReviewId, cancellationToken); + + if (diagnosis == null) throw new NotFoundException(nameof(Review), request.ReviewId); + if (diagnosis.IssueTicketId != request.IssueTicketId) + throw new NotFoundException($"Review of id {request.ReviewId} in Ticket {request.IssueTicketId} was not found"); + + return diagnosis; + } + } +} diff --git a/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs b/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs new file mode 100644 index 0000000..e166ece --- /dev/null +++ b/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs @@ -0,0 +1,60 @@ +using AutoMapper; +using AutoMapper.QueryableExtensions; +using CodeClinic.Application.Common.Exceptions; +using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Domain.Entities; +using MediatR; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace CodeClinic.Application.Reviews.Query.GetReviewList +{ + public class GetReviewListQuery : IRequest + { + public GetReviewListQuery(int issueTicketId) + { + IssueTicketId = issueTicketId; + } + public int IssueTicketId { get; private set; } + } + + public class GetDiagnosisListQueryHandler : IRequestHandler + { + private readonly IApplicationDbContext _context; + private readonly IMapper _mapper; + + public GetDiagnosisListQueryHandler(IApplicationDbContext context, IMapper mapper) + { + _context = context; + _mapper = mapper; + } + + public async Task Handle(GetReviewListQuery request, CancellationToken cancellationToken) + { + var diagnoses = await _context.Reviews.Where(it => it.IssueTicketId == request.IssueTicketId) + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(cancellationToken); + + + if (diagnoses == null) + throw new NotFoundException(nameof(Review), request.IssueTicketId); + + ReviewListVm vm = new ReviewListVm + { + Items = diagnoses + }; + + if (diagnoses != null) + { + return vm; + } + + return new ReviewListVm(); + } + } +} diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs b/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs new file mode 100644 index 0000000..6e82025 --- /dev/null +++ b/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using CodeClinic.Application.Common.Mappings; +using CodeClinic.Domain.Entities; + +namespace CodeClinic.Application.Reviews.Query.GetReviewList +{ + public class ReviewDto : IMapFrom + { + public int ReviewId { get; set; } + public int IssueTicketId { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(i => i.ReviewId, opt => opt.MapFrom(d => d.Id)); + } + + } +} diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs b/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs new file mode 100644 index 0000000..4b3a291 --- /dev/null +++ b/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace CodeClinic.Application.Reviews.Query.GetReviewList +{ + public class ReviewListVm + { + public List Items { get; set; } + } +} \ No newline at end of file diff --git a/src/Domain/Common/AuditableEntity.cs b/src/Domain/Common/AuditableEntity.cs index bbf7bea..4db861a 100644 --- a/src/Domain/Common/AuditableEntity.cs +++ b/src/Domain/Common/AuditableEntity.cs @@ -4,6 +4,7 @@ namespace CodeClinic.Domain.Common { public abstract class AuditableEntity { + public int Id { get; set; } public string CreatedBy { get; set; } public DateTime Created { get; set; } diff --git a/src/Domain/Entities/Category.cs b/src/Domain/Entities/Category.cs index 72a9590..bf28248 100644 --- a/src/Domain/Entities/Category.cs +++ b/src/Domain/Entities/Category.cs @@ -12,7 +12,6 @@ public Category() { IssueTickets = new List(); } - public int Id { get; set; } public string Name { get; set; } public string Description { get; set; } public IList IssueTickets { get; private set; } diff --git a/src/Domain/Entities/IssueTicket.cs b/src/Domain/Entities/IssueTicket.cs index 7d96454..e8e41e9 100644 --- a/src/Domain/Entities/IssueTicket.cs +++ b/src/Domain/Entities/IssueTicket.cs @@ -1,18 +1,20 @@ using CodeClinic.Domain.Common; using CodeClinic.Domain.Enums; +using System.Collections.Generic; namespace CodeClinic.Domain.Entities { public class IssueTicket : AuditableEntity { - public int Id { get; set; } public int CategoryId { get; set; } public string Title { get; set; } public int Stars { get; set; } public string Body { get; set; } - public Category Category { get; set; } public ProgressStatus Status { get; set; } + public Category Category { get; set; } + + public IList Reviews { get; set; } = new List(); } } \ No newline at end of file diff --git a/src/Domain/Entities/Review.cs b/src/Domain/Entities/Review.cs new file mode 100644 index 0000000..9c79a93 --- /dev/null +++ b/src/Domain/Entities/Review.cs @@ -0,0 +1,19 @@ +using CodeClinic.Domain.Common; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Domain.Entities +{ + public class Review :AuditableEntity + { + + public int IssueTicketId { get; set; } + + public string Title { get; set; } + + public string Description { get; set; } + + public IssueTicket IssueTicket { get; set; } + } +} diff --git a/src/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Infrastructure/Persistence/ApplicationDbContext.cs index 13ff9db..7e8430e 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContext.cs @@ -32,6 +32,7 @@ public ApplicationDbContext( public DbSet IssueTickets { get ; set ; } public DbSet Categories {get; set ; } + public DbSet Reviews { get ; set; } public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index dc86559..6486e88 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -1,21 +1,25 @@ using CodeClinic.Domain.Entities; using CodeClinic.Infrastructure.Identity; using Microsoft.AspNetCore.Identity; +using System; using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace CodeClinic.Infrastructure.Persistence { public static class ApplicationDbContextSeed { + private static ApplicationUser _defaultUser; + public static async Task SeedDefaultUserAsync(UserManager userManager) { - var defaultUser = new ApplicationUser { UserName = "admin@admin.com", Email = "admin@admin.com" }; + _defaultUser = new ApplicationUser { UserName = "admin@admin.com", Email = "admin@admin.com" }; - if (userManager.Users.All(u => u.UserName != defaultUser.UserName)) + if (userManager.Users.All(u => u.UserName != _defaultUser.UserName)) { - await userManager.CreateAsync(defaultUser, "P@ssw0rd"); + await userManager.CreateAsync(_defaultUser, "P@ssw0rd"); } } @@ -26,43 +30,77 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) { var categories = new List { - new Category{ Name = "Xamarin", Description ="Bug Issue Tickets related to Xamarin"}, - new Category{ Name = "AspNet Core ", Description ="Issue Tickets related to AspNet Core"}, - new Category{ Name = "Xamarin", Description ="Reported Bug issues for Blazor"}, - new Category{ Name = "ML.Net", Description ="Issues Related ML.Net"}, - new Category{ Name = "UWP", Description ="Bug Issue Tickets related to Universal Windows Platform"}, + new Category{CreatedBy=_defaultUser.Id, Name = "Xamarin", Description ="Bug Issue Tickets related to Xamarin"}, + new Category{CreatedBy=_defaultUser.Id, Name = "AspNet Core ", Description ="Issue Tickets related to AspNet Core"}, + new Category{CreatedBy=_defaultUser.Id, Name = "Xamarin", Description ="Reported Bug issues for Blazor"}, + new Category{CreatedBy=_defaultUser.Id, Name = "ML.Net", Description ="Issues Related ML.Net"}, + new Category{CreatedBy=_defaultUser.Id, Name = "UWP", Description ="Bug Issue Tickets related to Universal Windows Platform"}, }; context.Categories.AddRange(categories); await context.SaveChangesAsync(); + context.IssueTickets.AddRange( new List { new IssueTicket{ + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, Title = "😥 I broke my Clients Ecommece System Please Help", CategoryId = 1, - Body = "I was Updating to the latest version of entity framework and everything went west of westeros", + Status = Domain.Enums.ProgressStatus.InDiscussion, + Body = "I was Updating to the latest version of entity framework and everything went west of westeros", + Reviews = new List + { + new Review + { + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, + Title="Here is how you should debug your app", + Description ="If you were updating to 6.4 which is the latest current version at the time this was posted ,you should be aware that certain implementations where changed you I will show you where to " + + "change on your previous version else if this doesn help open the docs,should refer to the docs", + + } + } }, new IssueTicket{ + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, Title = "My Xamarin 📱 Application Has a Bug ,I cant Fix", CategoryId = 2, Body = "I cant seem to create a new page ", + Reviews = new List + { + new Review + { + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, + Title = "Provide more details", + Description="Can you describe the problem you facing " + + } + } }, new IssueTicket{ + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, Title = "😫 How do I integrate CICD on Web application", CategoryId = 3, Body = "Help hellp help ,I am frustrated", }, new IssueTicket{ + Created = DateTime.Now, + CreatedBy = _defaultUser.Id, Title = "Fix me! it Says ,But ☹ I dont know How", CategoryId = 4, Body = "Help me pleae ,Anyone someone ", }, } ); + await context.SaveChangesAsync(); } } diff --git a/src/Infrastructure/Persistence/Configurations/CategoryConfiguration.cs b/src/Infrastructure/Persistence/Configurations/CategoryConfiguration.cs index 52a0514..a62eab4 100644 --- a/src/Infrastructure/Persistence/Configurations/CategoryConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/CategoryConfiguration.cs @@ -13,6 +13,11 @@ public void Configure(EntityTypeBuilder builder) .IsRequired(); builder.Property(d => d.Description) .HasMaxLength(500); + + + builder.HasMany(it => it.IssueTickets) + .WithOne(x => x.Category) + .OnDelete(DeleteBehavior.Cascade); } } } diff --git a/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs b/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs index b409389..f58f797 100644 --- a/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs @@ -14,6 +14,14 @@ public void Configure(EntityTypeBuilder builder) builder.Property(t => t.Title) .HasMaxLength(200) .IsRequired(); + + builder.HasOne(c => c.Category) + .WithMany(it => it.IssueTickets) + .HasConstraintName("CategoryIssueTickets") + .OnDelete(DeleteBehavior.Restrict); + + builder.HasMany(d => d.Reviews) + .WithOne(it => it.IssueTicket); } } } diff --git a/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs new file mode 100644 index 0000000..85f5ba0 --- /dev/null +++ b/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs @@ -0,0 +1,29 @@ +using CodeClinic.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Infrastructure.Persistence.Configurations +{ + class ReviewConfiguration : IEntityTypeConfiguration + { + public void Configure(EntityTypeBuilder builder) + { + + builder.Property(t => t.Title); + builder + .Property(c => c.Description) + .IsRequired(); + + + builder.HasOne(x => x.IssueTicket) + .WithMany(d => d.Reviews) + .OnDelete(DeleteBehavior.Cascade) + .HasForeignKey(k => k.IssueTicketId) + .IsRequired(); + + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.cs b/src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.cs deleted file mode 100644 index 68799e9..0000000 --- a/src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Microsoft.EntityFrameworkCore.Migrations; - -namespace CodeClinic.Infrastructure.Persistence.Migrations -{ - public partial class EmptyMigration : Migration - { - protected override void Up(MigrationBuilder migrationBuilder) - { - - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - - } - } -} diff --git a/src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.Designer.cs b/src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.Designer.cs similarity index 89% rename from src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.Designer.cs rename to src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.Designer.cs index a8924a4..2130605 100644 --- a/src/Infrastructure/Persistence/Migrations/20201116080757_EmptyMigration.Designer.cs +++ b/src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.Designer.cs @@ -10,8 +10,8 @@ namespace CodeClinic.Infrastructure.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20201116080757_EmptyMigration")] - partial class EmptyMigration + [Migration("20201118041232_CreateReviewTable")] + partial class CreateReviewTable { protected override void BuildTargetModel(ModelBuilder modelBuilder) { @@ -97,6 +97,42 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("IssueTickets"); }); + modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IssueTicketId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IssueTicketId"); + + b.ToTable("Reviews"); + }); + modelBuilder.Entity("CodeClinic.Infrastructure.Identity.ApplicationUser", b => { b.Property("Id") @@ -384,6 +420,16 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasOne("CodeClinic.Domain.Entities.Category", "Category") .WithMany("IssueTickets") .HasForeignKey("CategoryId") + .HasConstraintName("CategoryIssueTickets") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => + { + b.HasOne("CodeClinic.Domain.Entities.IssueTicket", "IssueTicket") + .WithMany("Reviews") + .HasForeignKey("IssueTicketId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.cs b/src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.cs new file mode 100644 index 0000000..25007fc --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20201118041232_CreateReviewTable.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace CodeClinic.Infrastructure.Persistence.Migrations +{ + public partial class CreateReviewTable : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_IssueTickets_Categories_CategoryId", + table: "IssueTickets"); + + migrationBuilder.CreateTable( + name: "Reviews", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + CreatedBy = table.Column(nullable: true), + Created = table.Column(nullable: false), + LastModifiedBy = table.Column(nullable: true), + LastModified = table.Column(nullable: true), + IssueTicketId = table.Column(nullable: false), + Title = table.Column(nullable: true), + Description = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reviews", x => x.Id); + table.ForeignKey( + name: "FK_Reviews_IssueTickets_IssueTicketId", + column: x => x.IssueTicketId, + principalTable: "IssueTickets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_IssueTicketId", + table: "Reviews", + column: "IssueTicketId"); + + migrationBuilder.AddForeignKey( + name: "CategoryIssueTickets", + table: "IssueTickets", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "CategoryIssueTickets", + table: "IssueTickets"); + + migrationBuilder.DropTable( + name: "Reviews"); + + migrationBuilder.AddForeignKey( + name: "FK_IssueTickets_Categories_CategoryId", + table: "IssueTickets", + column: "CategoryId", + principalTable: "Categories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 139c67c..0f28a74 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -95,6 +95,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("IssueTickets"); }); + modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IssueTicketId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IssueTicketId"); + + b.ToTable("Reviews"); + }); + modelBuilder.Entity("CodeClinic.Infrastructure.Identity.ApplicationUser", b => { b.Property("Id") @@ -382,6 +418,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasOne("CodeClinic.Domain.Entities.Category", "Category") .WithMany("IssueTickets") .HasForeignKey("CategoryId") + .HasConstraintName("CategoryIssueTickets") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => + { + b.HasOne("CodeClinic.Domain.Entities.IssueTicket", "IssueTicket") + .WithMany("Reviews") + .HasForeignKey("IssueTicketId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); diff --git a/src/WebUI/ClientApp/angular.json b/src/WebUI/ClientApp/angular.json index fa03730..a1205f7 100644 --- a/src/WebUI/ClientApp/angular.json +++ b/src/WebUI/ClientApp/angular.json @@ -27,9 +27,11 @@ "styles": [ "./node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", + "src/styles.css" ], - "scripts": [] + "scripts": [ + ] }, "configurations": { "production": { @@ -156,4 +158,4 @@ } }, "defaultProject": "CodeClinic.WebUI" -} \ No newline at end of file +} diff --git a/src/WebUI/ClientApp/package-lock.json b/src/WebUI/ClientApp/package-lock.json index 3a7ba06..11a1492 100644 --- a/src/WebUI/ClientApp/package-lock.json +++ b/src/WebUI/ClientApp/package-lock.json @@ -5103,6 +5103,23 @@ "assert-plus": "^1.0.0" } }, + "datatables.net": { + "version": "1.10.22", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.22.tgz", + "integrity": "sha512-ujn8GvkQIBYzYH54XY7OrI0Zb35TKRd9ABYfbnXgBfwTGIFT6UsmXrfHU5Yk+MSDoF0sDu2TB+31V6c+zUZ0Pw==", + "requires": { + "jquery": ">=1.7" + } + }, + "datatables.net-bs4": { + "version": "1.10.22", + "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.22.tgz", + "integrity": "sha512-si0eOiaKmuURURpXhPRba7b3vCZsVfJK8pfrlM5WtaOaCEBa62DG/S9guMxUBmcAmvEC3FA2CKc/iKya3gb9qg==", + "requires": { + "datatables.net": "1.10.22", + "jquery": ">=1.7" + } + }, "date-format": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", diff --git a/src/WebUI/ClientApp/package.json b/src/WebUI/ClientApp/package.json index c31cf63..4fb4be5 100644 --- a/src/WebUI/ClientApp/package.json +++ b/src/WebUI/ClientApp/package.json @@ -28,6 +28,7 @@ "aspnet-prerendering": "^3.0.1", "bootstrap": "^4.3.1", "core-js": "^2.6.5", + "datatables.net-bs4": "^1.10.22", "jquery": "3.5.0", "ngx-bootstrap": "^5.2.0", "oidc-client": "^1.9.0", diff --git a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts index af4fcbc..32124e2 100644 --- a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts +++ b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts @@ -19,7 +19,7 @@ export interface ICategoriesClient { create(command: CreateCategoryCommand): Observable; getCategoryById(id: number): Observable; update(id: string, command: UpdateCategoryCommand): Observable; - delete(id: string, command: DeleteCategoryCommand): Observable; + delete(id: number): Observable; } @Injectable({ @@ -239,21 +239,17 @@ export class CategoriesClient implements ICategoriesClient { return _observableOf(null); } - delete(id: string, command: DeleteCategoryCommand): Observable { + delete(id: number): Observable { let url_ = this.baseUrl + "/api/Categories/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); url_ = url_.replace(/[?&]$/, ""); - const content_ = JSON.stringify(command); - let options_ : any = { - body: content_, observe: "response", responseType: "blob", headers: new HttpHeaders({ - "Content-Type": "application/json", "Accept": "application/octet-stream" }) }; @@ -296,7 +292,7 @@ export class CategoriesClient implements ICategoriesClient { export interface IIssueTicketsClient { getAll(): Observable; create(command: CreateIssueTicketCommand): Observable; - getIssueTicketById(id: number): Observable; + getIssueTicketById(id: number): Observable; update(id: number, command: UpdateIssueTicketCommand): Observable; delete(id: number): Observable; updateDetails(id: number | undefined, command: UpdateIssueTicketDetailsCommand): Observable; @@ -415,7 +411,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { return _observableOf(null); } - getIssueTicketById(id: number): Observable { + getIssueTicketById(id: number): Observable { let url_ = this.baseUrl + "/api/IssueTickets/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); @@ -437,14 +433,14 @@ export class IssueTicketsClient implements IIssueTicketsClient { try { return this.processGetIssueTicketById(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetIssueTicketById(response: HttpResponseBase): Observable { + protected processGetIssueTicketById(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -455,7 +451,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = IssueTicketDto.fromJS(resultData200); + result200 = IssueTicketDetailVm.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -463,7 +459,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } update(id: number, command: UpdateIssueTicketCommand): Observable { @@ -623,6 +619,294 @@ export class IssueTicketsClient implements IIssueTicketsClient { } } +export interface IReviewsClient { + getAll(issueTicketId: number): Observable; + create(issueTicketId: number, command: CreateReviewCommand): Observable; + getReviewById(issueTicketId: number, id: number): Observable; + update(id: number, issueTicketId: string, command: UpdateReviewCommand): Observable; + delete(issueTicketId: number, id: number): Observable; +} + +@Injectable({ + providedIn: 'root' +}) +export class ReviewsClient implements IReviewsClient { + private http: HttpClient; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) { + this.http = http; + this.baseUrl = baseUrl ? baseUrl : ""; + } + + getAll(issueTicketId: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + url_ = url_.replace(/[?&]$/, ""); + + let options_ : any = { + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Accept": "application/json" + }) + }; + + return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processGetAll(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processGetAll(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processGetAll(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = ReviewListVm.fromJS(resultData200); + return _observableOf(result200); + })); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } + + create(issueTicketId: number, command: CreateReviewCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(command); + + let options_ : any = { + body: content_, + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Content-Type": "application/json", + "Accept": "application/octet-stream" + }) + }; + + return this.http.request("post", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processCreate(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processCreate(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processCreate(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } + + getReviewById(issueTicketId: number, id: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + url_ = url_.replace(/[?&]$/, ""); + + let options_ : any = { + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Accept": "application/json" + }) + }; + + return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processGetReviewById(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processGetReviewById(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processGetReviewById(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = ReviewDto.fromJS(resultData200); + return _observableOf(result200); + })); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } + + update(id: number, issueTicketId: string, command: UpdateReviewCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(command); + + let options_ : any = { + body: content_, + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Content-Type": "application/json", + "Accept": "application/octet-stream" + }) + }; + + return this.http.request("put", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processUpdate(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processUpdate(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processUpdate(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } + + delete(issueTicketId: number, id: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + if (id === undefined || id === null) + throw new Error("The parameter 'id' must be defined."); + url_ = url_.replace("{id}", encodeURIComponent("" + id)); + url_ = url_.replace(/[?&]$/, ""); + + let options_ : any = { + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Accept": "application/octet-stream" + }) + }; + + return this.http.request("delete", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processDelete(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processDelete(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processDelete(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } +} + export class CategoryListVm implements ICategoryListVm { categories?: CategoryDto[] | undefined; @@ -943,42 +1227,6 @@ export interface IUpdateCategoryCommand { description?: string | undefined; } -export class DeleteCategoryCommand implements IDeleteCategoryCommand { - id?: number; - - constructor(data?: IDeleteCategoryCommand) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - this.id = _data["id"]; - } - } - - static fromJS(data: any): DeleteCategoryCommand { - data = typeof data === 'object' ? data : {}; - let result = new DeleteCategoryCommand(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["id"] = this.id; - return data; - } -} - -export interface IDeleteCategoryCommand { - id?: number; -} - export class IssueTicketListVm implements IIssueTicketListVm { progressStatuses?: ProgressStatusDto[] | undefined; issues?: IssueTicketDto[] | undefined; @@ -1075,6 +1323,132 @@ export interface IProgressStatusDto { name?: string | undefined; } +export class IssueTicketDetailVm implements IIssueTicketDetailVm { + categoryId?: number; + issueTicketId?: number; + title?: string | undefined; + stars?: number; + body?: string | undefined; + status?: ProgressStatus; + categoryName?: string | undefined; + reviews?: ReviewDto[] | undefined; + + constructor(data?: IIssueTicketDetailVm) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.categoryId = _data["categoryId"]; + this.issueTicketId = _data["issueTicketId"]; + this.title = _data["title"]; + this.stars = _data["stars"]; + this.body = _data["body"]; + this.status = _data["status"]; + this.categoryName = _data["categoryName"]; + if (Array.isArray(_data["reviews"])) { + this.reviews = [] as any; + for (let item of _data["reviews"]) + this.reviews!.push(ReviewDto.fromJS(item)); + } + } + } + + static fromJS(data: any): IssueTicketDetailVm { + data = typeof data === 'object' ? data : {}; + let result = new IssueTicketDetailVm(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["categoryId"] = this.categoryId; + data["issueTicketId"] = this.issueTicketId; + data["title"] = this.title; + data["stars"] = this.stars; + data["body"] = this.body; + data["status"] = this.status; + data["categoryName"] = this.categoryName; + if (Array.isArray(this.reviews)) { + data["reviews"] = []; + for (let item of this.reviews) + data["reviews"].push(item.toJSON()); + } + return data; + } +} + +export interface IIssueTicketDetailVm { + categoryId?: number; + issueTicketId?: number; + title?: string | undefined; + stars?: number; + body?: string | undefined; + status?: ProgressStatus; + categoryName?: string | undefined; + reviews?: ReviewDto[] | undefined; +} + +export enum ProgressStatus { + NotAnswered = 0, + InDiscussion = 1, + Answered = 2, +} + +export class ReviewDto implements IReviewDto { + reviewId?: number; + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: IReviewDto) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.reviewId = _data["reviewId"]; + this.issueTicketId = _data["issueTicketId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): ReviewDto { + data = typeof data === 'object' ? data : {}; + let result = new ReviewDto(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["reviewId"] = this.reviewId; + data["issueTicketId"] = this.issueTicketId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface IReviewDto { + reviewId?: number; + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; +} + export class CreateIssueTicketCommand implements ICreateIssueTicketCommand { id?: number; title?: string | undefined; @@ -1171,12 +1545,6 @@ export interface IUpdateIssueTicketCommand { status?: ProgressStatus; } -export enum ProgressStatus { - NotAnswered = 0, - InDiscussion = 1, - Answered = 2, -} - export class UpdateIssueTicketDetailsCommand implements IUpdateIssueTicketDetailsCommand { id?: number; title?: string | undefined; @@ -1225,6 +1593,142 @@ export interface IUpdateIssueTicketDetailsCommand { body?: string | undefined; } +export class ReviewListVm implements IReviewListVm { + items?: ReviewDto[] | undefined; + + constructor(data?: IReviewListVm) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + if (Array.isArray(_data["items"])) { + this.items = [] as any; + for (let item of _data["items"]) + this.items!.push(ReviewDto.fromJS(item)); + } + } + } + + static fromJS(data: any): ReviewListVm { + data = typeof data === 'object' ? data : {}; + let result = new ReviewListVm(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + if (Array.isArray(this.items)) { + data["items"] = []; + for (let item of this.items) + data["items"].push(item.toJSON()); + } + return data; + } +} + +export interface IReviewListVm { + items?: ReviewDto[] | undefined; +} + +export class CreateReviewCommand implements ICreateReviewCommand { + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: ICreateReviewCommand) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.issueTicketId = _data["issueTicketId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): CreateReviewCommand { + data = typeof data === 'object' ? data : {}; + let result = new CreateReviewCommand(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["issueTicketId"] = this.issueTicketId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface ICreateReviewCommand { + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; +} + +export class UpdateReviewCommand implements IUpdateReviewCommand { + issueTicketId?: number; + reviewId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: IUpdateReviewCommand) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.issueTicketId = _data["issueTicketId"]; + this.reviewId = _data["reviewId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): UpdateReviewCommand { + data = typeof data === 'object' ? data : {}; + let result = new UpdateReviewCommand(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["issueTicketId"] = this.issueTicketId; + data["reviewId"] = this.reviewId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface IUpdateReviewCommand { + issueTicketId?: number; + reviewId?: number; + title?: string | undefined; + description?: string | undefined; +} + export interface FileResponse { data: Blob; status: number; diff --git a/src/WebUI/ClientApp/src/app/app.module.ts b/src/WebUI/ClientApp/src/app/app.module.ts index 2cec15f..84bcd43 100644 --- a/src/WebUI/ClientApp/src/app/app.module.ts +++ b/src/WebUI/ClientApp/src/app/app.module.ts @@ -13,12 +13,14 @@ import { AuthorizeGuard } from 'src/api-authorization/authorize.guard'; import { AuthorizeInterceptor } from 'src/api-authorization/authorize.interceptor'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ModalModule } from 'ngx-bootstrap/modal'; +import { SummaryPipe } from '../pipes/summary/summary.pipe'; @NgModule({ declarations: [ AppComponent, NavMenuComponent, - HomeComponent + HomeComponent, + SummaryPipe ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), diff --git a/src/WebUI/ClientApp/src/app/home/home.component.html b/src/WebUI/ClientApp/src/app/home/home.component.html index 0ac018f..0916b77 100644 --- a/src/WebUI/ClientApp/src/app/home/home.component.html +++ b/src/WebUI/ClientApp/src/app/home/home.component.html @@ -1,44 +1,27 @@ + -
-
-
-

Welcome to the Code Clinic

-
-
-
-
    -
  • -
    -
    - {{ticket.title}} -
    -
    - -
    -
    - - - - - - - -
  • -
-
-
- -
+ + + + + + + + + + + + + + + + + + + + + + +
Issue TicketDescriptionCategoryStarsDate CreatedIssue StatusAction
{{ ticket.title | summary:30 }}{{ ticket.body | summary:60 }}{{ ticket.categoryName }}{{ ticket.stars }}{{ ticket.dateCreated | date:'mediumDate' }}{{ ticket.status }}
diff --git a/src/WebUI/ClientApp/src/app/home/home.component.ts b/src/WebUI/ClientApp/src/app/home/home.component.ts index 0a3c950..e7a8e9c 100644 --- a/src/WebUI/ClientApp/src/app/home/home.component.ts +++ b/src/WebUI/ClientApp/src/app/home/home.component.ts @@ -1,7 +1,7 @@ -import { Template } from '@angular/compiler/src/render3/r3_ast'; + import { Component, TemplateRef } from '@angular/core'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; -import { IIssueTicketDto, IssueTicketDto, IssueTicketListVm, IssueTicketsClient } from '../CodeClinic-api'; +import { IIssueTicketDto, IssueTicketDto, IssueTicketListVm, IssueTicketsClient, ProgressStatus } from '../CodeClinic-api'; @Component({ selector: 'app-home', @@ -14,18 +14,15 @@ export class HomeComponent { issueTicketsListvm: IssueTicketListVm; issuesTicketList: IIssueTicketDto[]; - constructor(private client: IssueTicketsClient, private modalService :BsModalService ) { - + this.client.getAll().subscribe(result => { this.issuesTicketList = result.issues; + }, error => console.error(error)); - } - onIssueTicketClick(modalTemplate: TemplateRef, issueTicket :IssueTicketDto) { - this.modalRef = this.modalService - .show(modalTemplate, - Object.assign({ issueTicket},{ class: 'gray modal-lg' })); } + + } diff --git a/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.spec.ts b/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.spec.ts new file mode 100644 index 0000000..fd8e32f --- /dev/null +++ b/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.spec.ts @@ -0,0 +1,8 @@ +import { SummaryPipe } from './summary.pipe'; + +describe('SummaryPipe', () => { + it('create an instance', () => { + const pipe = new SummaryPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.ts b/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.ts new file mode 100644 index 0000000..802ac01 --- /dev/null +++ b/src/WebUI/ClientApp/src/pipes/summary/summary.pipe.ts @@ -0,0 +1,18 @@ +import { ProgressStatusDto } from './../../app/CodeClinic-api'; +import { Pipe, PipeTransform } from '@angular/core'; +import { ProgressStatus } from '../../app/CodeClinic-api'; + +@Pipe({ + name: 'summary' +}) +export class SummaryPipe implements PipeTransform { + + transform(value: string, length?: number): string{ + + + return value.substr(0, length != null ? length : 50)+ "..."; + } + + + +} diff --git a/src/WebUI/Controllers/CategoriesController.cs b/src/WebUI/Controllers/CategoriesController.cs index 1a6388a..e3079e3 100644 --- a/src/WebUI/Controllers/CategoriesController.cs +++ b/src/WebUI/Controllers/CategoriesController.cs @@ -3,6 +3,7 @@ using CodeClinic.Application.Categories.Commands.UpdateCategory; using CodeClinic.Application.Categories.Queries.GetCategory; using CodeClinic.Application.Categories.Queries.GetCategoryList; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; @@ -26,6 +27,8 @@ public async Task> GetCategoryById(int id) return await Mediator.Send(new GetCategoryDetailQuery { Id = id }); } + + [Authorize] [HttpPost] public async Task> Create(CreateCategoryCommand command) { @@ -42,9 +45,9 @@ public async Task Update(UpdateCategoryCommand command) [HttpDelete("{id}")] - public async Task Delete(DeleteCategoryCommand command) + public async Task Delete(int id) { - await Mediator.Send(command); + await Mediator.Send(new DeleteCategoryCommand { Id =id}); return NoContent(); } diff --git a/src/WebUI/Controllers/IssueTicketsController.cs b/src/WebUI/Controllers/IssueTicketsController.cs index f921b02..054bc18 100644 --- a/src/WebUI/Controllers/IssueTicketsController.cs +++ b/src/WebUI/Controllers/IssueTicketsController.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using CodeClinic.Application.IssueTickets.Commands.UpdateIssueTicket; using CodeClinic.Application.IssueItems.Queries.GetIssueDetail; +using CodeClinic.Application.IssueTickets.Queries.GetIssueDetail; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 @@ -21,13 +22,16 @@ public async Task> GetAll() return await Mediator.Send(new GetIssueTicketListQuery()); } + + [HttpGet("{id}")] - public async Task> GetIssueTicketById(int id) + public async Task> GetIssueTicketById(int id) { return await Mediator.Send(new GetIssueTicketDetailQuery { Id = id }); } + [HttpPost] public async Task> Create(CreateIssueTicketCommand command) { diff --git a/src/WebUI/Controllers/ReviewsController.cs b/src/WebUI/Controllers/ReviewsController.cs new file mode 100644 index 0000000..967b2c1 --- /dev/null +++ b/src/WebUI/Controllers/ReviewsController.cs @@ -0,0 +1,52 @@ +using System.Security.Cryptography; +using CodeClinic.Application.Reviews.Query.GetReviewList; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using CodeClinic.Application.Reviews.Commands.CreateReview; +using CodeClinic.Application.Reviews.Commands.UpdateReview; +using CodeClinic.Application.Reviews.Commands.DeleteReview; +using CodeClinic.Application.Reviews.Query.GetReviewDetails; + +namespace CodeClinic.WebUI.Controllers +{ + [Route("api/issueTickets/{issueTicketId}/[controller]")] + public class ReviewsController : ApiController + { + + [HttpGet] + public async Task> GetAll(int issueTicketId) + { + return await Mediator.Send(new GetReviewListQuery(issueTicketId)); + } + + [HttpGet("{id}")] + public async Task> GetReviewById(int issueTicketId,int id) + { + return await Mediator.Send(new GetReviewDetailsQuery { IssueTicketId = issueTicketId,ReviewId = id }); + } + + [HttpPost] + public async Task Create(int issueTicketId, CreateReviewCommand command) + { + if (issueTicketId != command.IssueTicketId) + return BadRequest(); + return Ok(await Mediator.Send(command)); + } + + + [HttpPut("{id}")] + public async Task Update(int id, UpdateReviewCommand command) + { + await Mediator.Send(command); + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task Delete(int issueTicketId,int id) + { + await Mediator.Send(new DeleteReviewCommand {IssueTicketId = issueTicketId, Id = id}); + return NoContent(); + } + } +} diff --git a/src/WebUI/wwwroot/api/specification.json b/src/WebUI/wwwroot/api/specification.json index 1277e62..10e089c 100644 --- a/src/WebUI/wwwroot/api/specification.json +++ b/src/WebUI/wwwroot/api/specification.json @@ -54,7 +54,12 @@ } } } - } + }, + "security": [ + { + "JWT": [] + } + ] } }, "/api/Categories/{id}": { @@ -141,23 +146,12 @@ "in": "path", "required": true, "schema": { - "type": "string" + "type": "integer", + "format": "int32" }, - "x-position": 2 + "x-position": 1 } ], - "requestBody": { - "x-name": "command", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/DeleteCategoryCommand" - } - } - }, - "required": true, - "x-position": 1 - }, "responses": { "200": { "description": "", @@ -258,7 +252,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssueTicketDto" + "$ref": "#/components/schemas/IssueTicketDetailVm" } } } @@ -403,6 +397,216 @@ } ] } + }, + "/api/issueTickets/{issueTicketId}/Reviews": { + "get": { + "tags": [ + "Reviews" + ], + "operationId": "Reviews_GetAll", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewListVm" + } + } + } + } + } + }, + "post": { + "tags": [ + "Reviews" + ], + "operationId": "Reviews_Create", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } + ], + "requestBody": { + "x-name": "command", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateReviewCommand" + } + } + }, + "required": true, + "x-position": 2 + }, + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/api/issueTickets/{issueTicketId}/Reviews/{id}": { + "get": { + "tags": [ + "Reviews" + ], + "operationId": "Reviews_GetReviewById", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 2 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + } + } + }, + "put": { + "tags": [ + "Reviews" + ], + "operationId": "Reviews_Update", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + }, + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "x-position": 3 + } + ], + "requestBody": { + "x-name": "command", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateReviewCommand" + } + } + }, + "required": true, + "x-position": 2 + }, + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "delete": { + "tags": [ + "Reviews" + ], + "operationId": "Reviews_Delete", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 2 + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } } }, "components": { @@ -563,16 +767,6 @@ } } }, - "DeleteCategoryCommand": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "integer", - "format": "int32" - } - } - }, "IssueTicketListVm": { "type": "object", "additionalProperties": false, @@ -607,6 +801,82 @@ } } }, + "IssueTicketDetailVm": { + "type": "object", + "additionalProperties": false, + "properties": { + "categoryId": { + "type": "integer", + "format": "int32" + }, + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "stars": { + "type": "integer", + "format": "int32" + }, + "body": { + "type": "string", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ProgressStatus" + }, + "categoryName": { + "type": "string", + "nullable": true + }, + "reviews": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + }, + "ProgressStatus": { + "type": "integer", + "description": "", + "x-enumNames": [ + "NotAnswered", + "InDiscussion", + "Answered" + ], + "enum": [ + 0, + 1, + 2 + ] + }, + "ReviewDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "reviewId": { + "type": "integer", + "format": "int32" + }, + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } + }, "CreateIssueTicketCommand": { "type": "object", "additionalProperties": false, @@ -650,20 +920,6 @@ } } }, - "ProgressStatus": { - "type": "integer", - "description": "", - "x-enumNames": [ - "NotAnswered", - "InDiscussion", - "Answered" - ], - "enum": [ - 0, - 1, - 2 - ] - }, "UpdateIssueTicketDetailsCommand": { "type": "object", "additionalProperties": false, @@ -685,6 +941,59 @@ "nullable": true } } + }, + "ReviewListVm": { + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + }, + "CreateReviewCommand": { + "type": "object", + "additionalProperties": false, + "properties": { + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } + }, + "UpdateReviewCommand": { + "type": "object", + "additionalProperties": false, + "properties": { + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "reviewId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } } }, "securitySchemes": { diff --git a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs b/tests/Application.UnitTests/Common/Mappings/MappingTests.cs index e4e5a2b..01bb74d 100644 --- a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs +++ b/tests/Application.UnitTests/Common/Mappings/MappingTests.cs @@ -2,6 +2,7 @@ using CodeClinic.Application.Categories.Queries.GetCategoryList; using CodeClinic.Application.Common.Mappings; using CodeClinic.Application.Issues.Queries.GetIssueList; +using CodeClinic.Application.Reviews.Query.GetReviewList; using CodeClinic.Domain.Entities; using NUnit.Framework; using System; @@ -32,6 +33,7 @@ public void ShouldHaveValidConfiguration() [Test] [TestCase(typeof(IssueTicket), typeof(IssueTicketDto))] [TestCase(typeof(Category), typeof(CategoryDto))] + [TestCase(typeof(Review), typeof(ReviewDto))] public void ShouldSupportMappingFromSourceToDestination(Type source, Type destination) { var instance = Activator.CreateInstance(source); From e6c61efd0a26f5768b790af659db9cd278e926d4 Mon Sep 17 00:00:00 2001 From: NICOLAS MALULEKE Date: Thu, 19 Nov 2020 14:04:05 +0200 Subject: [PATCH 02/11] Changed Review to Comment --- .../Interfaces/IApplicationDbContext.cs | 2 +- .../CreateIssueTicketCommandValidator.cs | 7 +- .../GetIssueTicketDetailQuery.cs | 8 +- .../GetIssueDetail/IssueTicketDetailVm.cs | 4 +- .../CreateReview/CreateReviewCommand.cs | 16 +- .../CreateReviewCommandValidator.cs | 6 +- .../DeleteReview/DeleteReviewCommand.cs | 16 +- .../DeleteReviewCommandValidator.cs | 16 +- .../Commands/ReviewCommandBaseHandler.cs | 6 +- .../CreateReviewCommandValidator.cs | 8 +- .../UpdateReview/UpdateReviewCommand.cs | 18 +- .../GetReviewDetails/GetReviewDetailsQuery.cs | 24 +- .../Query/GetReviewList/GetReviewListQuery.cs | 20 +- .../Reviews/Query/GetReviewList/ReviewDto.cs | 10 +- .../Query/GetReviewList/ReviewListVm.cs | 6 +- src/Domain/Entities/IssueTicket.cs | 2 +- src/Domain/Entities/Review.cs | 2 +- .../Persistence/ApplicationDbContext.cs | 2 +- .../Persistence/ApplicationDbContextSeed.cs | 8 +- .../IssueTicketConfiguration.cs | 2 +- .../Configurations/ReviewConfiguration.cs | 6 +- ...9120107_ChangedReviewToComment.Designer.cs | 490 ++++++++++++++++++ .../20201119120107_ChangedReviewToComment.cs | 80 +++ .../ApplicationDbContextModelSnapshot.cs | 76 +-- src/WebUI/ClientApp/src/app/CodeClinic-api.ts | 136 ++--- src/WebUI/Controllers/ReviewsController.cs | 26 +- src/WebUI/wwwroot/api/specification.json | 50 +- .../Common/Mappings/MappingTests.cs | 4 +- 28 files changed, 811 insertions(+), 240 deletions(-) create mode 100644 src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.Designer.cs create mode 100644 src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.cs diff --git a/src/Application/Common/Interfaces/IApplicationDbContext.cs b/src/Application/Common/Interfaces/IApplicationDbContext.cs index 655dea1..2523a08 100644 --- a/src/Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/Application/Common/Interfaces/IApplicationDbContext.cs @@ -10,7 +10,7 @@ public interface IApplicationDbContext DbSet IssueTickets {get;set;} DbSet Categories { get; set; } - DbSet Reviews { get; set; } + DbSet Comments { get; set; } Task SaveChangesAsync(CancellationToken cancellationToken); diff --git a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs index 9679e71..376274b 100644 --- a/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs +++ b/src/Application/IssueTickets/Commands/CreateIssueTicket/CreateIssueTicketCommandValidator.cs @@ -1,5 +1,6 @@ using Application.Issues.Commands.CreateIssue; using CodeClinic.Application.Common.Interfaces; +using CodeClinic.Application.Common.Models; using FluentValidation; using Microsoft.EntityFrameworkCore; using System.Threading; @@ -16,13 +17,13 @@ public CreateIssueTicketCommandValidator(IApplicationDbContext context) RuleFor(v => v.Title) .MaximumLength(200) - .NotEmpty() + .NotEmpty().WithErrorCode(ErrorCode.BadRequest) .WithMessage("Title cannot be longer than 200 characters"); RuleFor(c => c.CategoryId) .NotNull() - .GreaterThan(0) - .MustAsync(HaveAnExistingCategory) + .GreaterThan(0) + .MustAsync(HaveAnExistingCategory).WithErrorCode(ErrorCode.NotFound) .WithMessage("Choose a Category that exists in the system"); _context = context; } diff --git a/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs b/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs index fe172e7..5639271 100644 --- a/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs +++ b/src/Application/IssueTickets/Queries/GetIssueDetail/GetIssueTicketDetailQuery.cs @@ -4,7 +4,7 @@ using CodeClinic.Application.Common.Interfaces; using CodeClinic.Application.Issues.Queries.GetIssueList; using CodeClinic.Application.IssueTickets.Queries.GetIssueDetail; -using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Application.Comments.Query.GetCommentList; using CodeClinic.Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; @@ -38,11 +38,11 @@ public async Task Handle(GetIssueTicketDetailQuery request, if(viewModel == null) throw new NotFoundException(nameof(IssueTicket), request.Id); - var reviews = await _context.Reviews - .ProjectTo(_mapper.ConfigurationProvider) + var Comments = await _context.Comments + .ProjectTo(_mapper.ConfigurationProvider) .Where(x => x.IssueTicketId == request.Id).ToListAsync(cancellationToken); - viewModel.Reviews = reviews; + viewModel.Comments = Comments; return viewModel; } } diff --git a/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs b/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs index 99e3763..bfa17dc 100644 --- a/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs +++ b/src/Application/IssueTickets/Queries/GetIssueDetail/IssueTicketDetailVm.cs @@ -1,6 +1,6 @@ using AutoMapper; using CodeClinic.Application.Common.Mappings; -using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Application.Comments.Query.GetCommentList; using CodeClinic.Domain.Entities; using CodeClinic.Domain.Enums; using System; @@ -20,7 +20,7 @@ public class IssueTicketDetailVm:IMapFrom public ProgressStatus Status { get; set; } public string CategoryName { get; set; } - public IList Reviews { get; set; } = new List(); + public IList Comments { get; set; } = new List(); public void Mapping(Profile profile) diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs index a931753..7c46140 100644 --- a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs +++ b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs @@ -8,9 +8,9 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Commands.CreateReview +namespace CodeClinic.Application.Comments.Commands.CreateComment { - public class CreateReviewCommand : IRequest + public class CreateCommentCommand : IRequest { public int IssueTicketId { get; set; } public string Title { get; set; } @@ -19,28 +19,28 @@ public class CreateReviewCommand : IRequest } - public class CreateReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + public class CreateCommentCommandHandler : CommentCommandBaseHandler, IRequestHandler { - public CreateReviewCommandHandler(IApplicationDbContext context) : base(context) { } + public CreateCommentCommandHandler(IApplicationDbContext context) : base(context) { } - public async Task Handle(CreateReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(CreateCommentCommand request, CancellationToken cancellationToken) { var issueTicket = await _context.IssueTickets.FindAsync(request.IssueTicketId); if (issueTicket == null) throw - new PostException(nameof(Review), + new PostException(nameof(Comment), new NotFoundException($"The Depend Entity '{nameof(IssueTicket)}'" + $" with Id {request.IssueTicketId} was not found")); - var entity = new Review + var entity = new Comment { IssueTicketId = request.IssueTicketId, Title = request.Title, Description = request.Description }; - _context.Reviews.Add(entity); + _context.Comments.Add(entity); await _context.SaveChangesAsync(cancellationToken); return entity.Id; diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs index c5c3814..1a918d5 100644 --- a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs +++ b/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs @@ -4,11 +4,11 @@ using System.Collections.Generic; using System.Text; -namespace CodeClinic.Application.Reviews.Commands.CreateReview +namespace CodeClinic.Application.Comments.Commands.CreateComment { - class CreateReviewCommandValidator: AbstractValidator + class CreateCommentCommandValidator: AbstractValidator { - public CreateReviewCommandValidator() + public CreateCommentCommandValidator() { RuleFor(t => t.IssueTicketId) .NotEmpty().NotNull() diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs index cd4fec8..6d07f05 100644 --- a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs +++ b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs @@ -8,27 +8,27 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Commands.DeleteReview +namespace CodeClinic.Application.Comments.Commands.DeleteComment { - public class DeleteReviewCommand : IRequest + public class DeleteCommentCommand : IRequest { public int IssueTicketId { get; set; } public int Id { get; set; } } - public class DeleteReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + public class DeleteCommentCommandHandler : CommentCommandBaseHandler, IRequestHandler { - public DeleteReviewCommandHandler(IApplicationDbContext context) : base(context) { } + public DeleteCommentCommandHandler(IApplicationDbContext context) : base(context) { } - public async Task Handle(DeleteReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(DeleteCommentCommand request, CancellationToken cancellationToken) { - var entity = await _context.Reviews.FindAsync(request.Id); + var entity = await _context.Comments.FindAsync(request.Id); - if (entity == null) throw new NotFoundException(nameof(Review), request.Id); + if (entity == null) throw new NotFoundException(nameof(Comment), request.Id); - _context.Reviews.Remove(entity); + _context.Comments.Remove(entity); await _context.SaveChangesAsync(cancellationToken); diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs index 6b50da2..60ed3da 100644 --- a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs +++ b/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs @@ -8,29 +8,29 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Commands.DeleteReview +namespace CodeClinic.Application.Comments.Commands.DeleteComment { - class DeleteReviewCommandValidator :AbstractValidator + class DeleteCommentCommandValidator :AbstractValidator { private readonly IApplicationDbContext _context; - public DeleteReviewCommandValidator(IApplicationDbContext context) + public DeleteCommentCommandValidator(IApplicationDbContext context) { _context = context; } - public DeleteReviewCommandValidator() + public DeleteCommentCommandValidator() { RuleFor(i => i.Id) - .NotEmpty().NotNull().WithMessage("Review Id is Required!"); + .NotEmpty().NotNull().WithMessage("Comment Id is Required!"); RuleFor(i => i.IssueTicketId).NotEmpty() .MustAsync(MustBeInTheIssueTicket) - .WithMessage("Ticket does not contain review with given Id!"); + .WithMessage("Ticket does not contain Comment with given Id!"); } - public async Task MustBeInTheIssueTicket(DeleteReviewCommand request, int issueTickeId, CancellationToken cancellationToken) + public async Task MustBeInTheIssueTicket(DeleteCommentCommand request, int issueTickeId, CancellationToken cancellationToken) { - var ticket = await _context.IssueTickets.FirstOrDefaultAsync(i => i.Id == request.IssueTicketId && i.Reviews.FirstOrDefault(r => r.IssueTicketId == issueTickeId ) != null); + var ticket = await _context.IssueTickets.FirstOrDefaultAsync(i => i.Id == request.IssueTicketId && i.Comments.FirstOrDefault(r => r.IssueTicketId == issueTickeId ) != null); return ticket != null; } } diff --git a/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs b/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs index d6b7ff3..e2b7a26 100644 --- a/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs +++ b/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs @@ -4,13 +4,13 @@ using System.Collections.Generic; using System.Text; -namespace CodeClinic.Application.Reviews.Commands +namespace CodeClinic.Application.Comments.Commands { - public class ReviewCommandBaseHandler + public class CommentCommandBaseHandler { protected readonly IApplicationDbContext _context; - public ReviewCommandBaseHandler(IApplicationDbContext context) + public CommentCommandBaseHandler(IApplicationDbContext context) { _context = context; } diff --git a/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs b/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs index e4cf65a..525d757 100644 --- a/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs +++ b/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Text; -namespace CodeClinic.Application.Reviews.Commands.UpdateReview +namespace CodeClinic.Application.Comments.Commands.UpdateComment { - class UpdateReviewCommandValidator : AbstractValidator + class UpdateCommentCommandValidator : AbstractValidator { - public UpdateReviewCommandValidator() + public UpdateCommentCommandValidator() { - RuleFor(i => i.ReviewId).NotEmpty() + RuleFor(i => i.CommentId).NotEmpty() .NotNull(); RuleFor(t => t.IssueTicketId) diff --git a/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs b/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs index 24570c7..0fb1549 100644 --- a/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs +++ b/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs @@ -9,30 +9,30 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Commands.UpdateReview +namespace CodeClinic.Application.Comments.Commands.UpdateComment { - public class UpdateReviewCommand : IRequest + public class UpdateCommentCommand : IRequest { public int IssueTicketId { get; set; } - public int ReviewId { get; set; } + public int CommentId { get; set; } public string Title { get; set; } public string Description { get; set; } } - public class UpdateReviewCommandHandler : ReviewCommandBaseHandler, IRequestHandler + public class UpdateCommentCommandHandler : CommentCommandBaseHandler, IRequestHandler { - public UpdateReviewCommandHandler(IApplicationDbContext context) : base(context) { } + public UpdateCommentCommandHandler(IApplicationDbContext context) : base(context) { } - public async Task Handle(UpdateReviewCommand request, CancellationToken cancellationToken) + public async Task Handle(UpdateCommentCommand request, CancellationToken cancellationToken) { - Review entity = await _context.Reviews.FindAsync(request.ReviewId); + Comment entity = await _context.Comments.FindAsync(request.CommentId); - if (entity == null) throw new NotFoundException(nameof(Review), request.ReviewId); + if (entity == null) throw new NotFoundException(nameof(Comment), request.CommentId); entity.Title = request.Title; entity.Description = request.Description; - _context.Reviews.Update(entity); + _context.Comments.Update(entity); await _context.SaveChangesAsync(cancellationToken); return Unit.Value; diff --git a/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs b/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs index 2d283ba..2fe49c1 100644 --- a/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs +++ b/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs @@ -2,7 +2,7 @@ using AutoMapper.QueryableExtensions; using CodeClinic.Application.Common.Exceptions; using CodeClinic.Application.Common.Interfaces; -using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Application.Comments.Query.GetCommentList; using CodeClinic.Domain.Entities; using MediatR; using Microsoft.EntityFrameworkCore; @@ -12,33 +12,33 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Query.GetReviewDetails +namespace CodeClinic.Application.Comments.Query.GetCommentDetails { - public class GetReviewDetailsQuery : IRequest + public class GetCommentDetailsQuery : IRequest { public int IssueTicketId { get; set; } - public int ReviewId { get; set; } + public int CommentId { get; set; } } - public class GetReviewDetailsQueryHandler : IRequestHandler + public class GetCommentDetailsQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; - public GetReviewDetailsQueryHandler(IApplicationDbContext context, IMapper mapper) + public GetCommentDetailsQueryHandler(IApplicationDbContext context, IMapper mapper) { _context = context; _mapper = mapper; } - public async Task Handle(GetReviewDetailsQuery request, CancellationToken cancellationToken) + public async Task Handle(GetCommentDetailsQuery request, CancellationToken cancellationToken) { - var diagnosis = await _context.Reviews - .ProjectTo(_mapper.ConfigurationProvider) - .SingleOrDefaultAsync(i => i.ReviewId == request.ReviewId, cancellationToken); + var diagnosis = await _context.Comments + .ProjectTo(_mapper.ConfigurationProvider) + .SingleOrDefaultAsync(i => i.CommentId == request.CommentId, cancellationToken); - if (diagnosis == null) throw new NotFoundException(nameof(Review), request.ReviewId); + if (diagnosis == null) throw new NotFoundException(nameof(Comment), request.CommentId); if (diagnosis.IssueTicketId != request.IssueTicketId) - throw new NotFoundException($"Review of id {request.ReviewId} in Ticket {request.IssueTicketId} was not found"); + throw new NotFoundException($"Comment of id {request.CommentId} in Ticket {request.IssueTicketId} was not found"); return diagnosis; } diff --git a/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs b/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs index e166ece..6aabf0c 100644 --- a/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs +++ b/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs @@ -12,18 +12,18 @@ using System.Threading; using System.Threading.Tasks; -namespace CodeClinic.Application.Reviews.Query.GetReviewList +namespace CodeClinic.Application.Comments.Query.GetCommentList { - public class GetReviewListQuery : IRequest + public class GetCommentListQuery : IRequest { - public GetReviewListQuery(int issueTicketId) + public GetCommentListQuery(int issueTicketId) { IssueTicketId = issueTicketId; } public int IssueTicketId { get; private set; } } - public class GetDiagnosisListQueryHandler : IRequestHandler + public class GetDiagnosisListQueryHandler : IRequestHandler { private readonly IApplicationDbContext _context; private readonly IMapper _mapper; @@ -34,17 +34,17 @@ public GetDiagnosisListQueryHandler(IApplicationDbContext context, IMapper mappe _mapper = mapper; } - public async Task Handle(GetReviewListQuery request, CancellationToken cancellationToken) + public async Task Handle(GetCommentListQuery request, CancellationToken cancellationToken) { - var diagnoses = await _context.Reviews.Where(it => it.IssueTicketId == request.IssueTicketId) - .ProjectTo(_mapper.ConfigurationProvider) + var diagnoses = await _context.Comments.Where(it => it.IssueTicketId == request.IssueTicketId) + .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(cancellationToken); if (diagnoses == null) - throw new NotFoundException(nameof(Review), request.IssueTicketId); + throw new NotFoundException(nameof(Comment), request.IssueTicketId); - ReviewListVm vm = new ReviewListVm + CommentListVm vm = new CommentListVm { Items = diagnoses }; @@ -54,7 +54,7 @@ public async Task Handle(GetReviewListQuery request, CancellationT return vm; } - return new ReviewListVm(); + return new CommentListVm(); } } } diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs b/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs index 6e82025..0099c8d 100644 --- a/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs +++ b/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs @@ -2,11 +2,11 @@ using CodeClinic.Application.Common.Mappings; using CodeClinic.Domain.Entities; -namespace CodeClinic.Application.Reviews.Query.GetReviewList +namespace CodeClinic.Application.Comments.Query.GetCommentList { - public class ReviewDto : IMapFrom + public class CommentDto : IMapFrom { - public int ReviewId { get; set; } + public int CommentId { get; set; } public int IssueTicketId { get; set; } public string Title { get; set; } @@ -14,8 +14,8 @@ public class ReviewDto : IMapFrom public string Description { get; set; } public void Mapping(Profile profile) { - profile.CreateMap() - .ForMember(i => i.ReviewId, opt => opt.MapFrom(d => d.Id)); + profile.CreateMap() + .ForMember(i => i.CommentId, opt => opt.MapFrom(d => d.Id)); } } diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs b/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs index 4b3a291..06b7731 100644 --- a/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs +++ b/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; -namespace CodeClinic.Application.Reviews.Query.GetReviewList +namespace CodeClinic.Application.Comments.Query.GetCommentList { - public class ReviewListVm + public class CommentListVm { - public List Items { get; set; } + public List Items { get; set; } } } \ No newline at end of file diff --git a/src/Domain/Entities/IssueTicket.cs b/src/Domain/Entities/IssueTicket.cs index e8e41e9..b86ffcd 100644 --- a/src/Domain/Entities/IssueTicket.cs +++ b/src/Domain/Entities/IssueTicket.cs @@ -15,6 +15,6 @@ public class IssueTicket : AuditableEntity public ProgressStatus Status { get; set; } public Category Category { get; set; } - public IList Reviews { get; set; } = new List(); + public IList Comments { get; set; } = new List(); } } \ No newline at end of file diff --git a/src/Domain/Entities/Review.cs b/src/Domain/Entities/Review.cs index 9c79a93..e2de058 100644 --- a/src/Domain/Entities/Review.cs +++ b/src/Domain/Entities/Review.cs @@ -5,7 +5,7 @@ namespace CodeClinic.Domain.Entities { - public class Review :AuditableEntity + public class Comment :AuditableEntity { public int IssueTicketId { get; set; } diff --git a/src/Infrastructure/Persistence/ApplicationDbContext.cs b/src/Infrastructure/Persistence/ApplicationDbContext.cs index 7e8430e..6c00e9d 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContext.cs @@ -32,7 +32,7 @@ public ApplicationDbContext( public DbSet IssueTickets { get ; set ; } public DbSet Categories {get; set ; } - public DbSet Reviews { get ; set; } + public DbSet Comments { get ; set; } public override Task SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken()) { diff --git a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs index 6486e88..4c75ae7 100644 --- a/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs +++ b/src/Infrastructure/Persistence/ApplicationDbContextSeed.cs @@ -51,9 +51,9 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) CategoryId = 1, Status = Domain.Enums.ProgressStatus.InDiscussion, Body = "I was Updating to the latest version of entity framework and everything went west of westeros", - Reviews = new List + Comments = new List { - new Review + new Comment { Created = DateTime.Now, CreatedBy = _defaultUser.Id, @@ -71,9 +71,9 @@ public static async Task SeedSampleDataAsync(ApplicationDbContext context) Title = "My Xamarin 📱 Application Has a Bug ,I cant Fix", CategoryId = 2, Body = "I cant seem to create a new page ", - Reviews = new List + Comments = new List { - new Review + new Comment { Created = DateTime.Now, CreatedBy = _defaultUser.Id, diff --git a/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs b/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs index f58f797..bc0cf25 100644 --- a/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/IssueTicketConfiguration.cs @@ -20,7 +20,7 @@ public void Configure(EntityTypeBuilder builder) .HasConstraintName("CategoryIssueTickets") .OnDelete(DeleteBehavior.Restrict); - builder.HasMany(d => d.Reviews) + builder.HasMany(d => d.Comments) .WithOne(it => it.IssueTicket); } } diff --git a/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs b/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs index 85f5ba0..aeefca2 100644 --- a/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs +++ b/src/Infrastructure/Persistence/Configurations/ReviewConfiguration.cs @@ -7,9 +7,9 @@ namespace CodeClinic.Infrastructure.Persistence.Configurations { - class ReviewConfiguration : IEntityTypeConfiguration + class CommentConfiguration : IEntityTypeConfiguration { - public void Configure(EntityTypeBuilder builder) + public void Configure(EntityTypeBuilder builder) { builder.Property(t => t.Title); @@ -19,7 +19,7 @@ public void Configure(EntityTypeBuilder builder) builder.HasOne(x => x.IssueTicket) - .WithMany(d => d.Reviews) + .WithMany(d => d.Comments) .OnDelete(DeleteBehavior.Cascade) .HasForeignKey(k => k.IssueTicketId) .IsRequired(); diff --git a/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.Designer.cs b/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.Designer.cs new file mode 100644 index 0000000..c1c618a --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.Designer.cs @@ -0,0 +1,490 @@ +// +using System; +using CodeClinic.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CodeClinic.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20201119120107_ChangedReviewToComment")] + partial class ChangedReviewToComment + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(500)") + .HasMaxLength(500); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(30)") + .HasMaxLength(30); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IssueTicketId") + .HasColumnType("int"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("IssueTicketId"); + + b.ToTable("Comments"); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Body") + .HasColumnType("nvarchar(max)"); + + b.Property("CategoryId") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.ToTable("IssueTickets"); + }); + + modelBuilder.Entity("CodeClinic.Infrastructure.Identity.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .HasColumnType("bit"); + + b.Property("LockoutEnabled") + .HasColumnType("bit"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit"); + + b.Property("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex") + .HasFilter("[NormalizedUserName] IS NOT NULL"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("DeviceCode") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("datetime2"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("CreationTime") + .HasColumnType("datetime2"); + + b.Property("Data") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(50000); + + b.Property("Expiration") + .HasColumnType("datetime2"); + + b.Property("SubjectId") + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(50)") + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex") + .HasFilter("[NormalizedName] IS NOT NULL"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Comment", b => + { + b.HasOne("CodeClinic.Domain.Entities.IssueTicket", "IssueTicket") + .WithMany("Comments") + .HasForeignKey("IssueTicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => + { + b.HasOne("CodeClinic.Domain.Entities.Category", "Category") + .WithMany("IssueTickets") + .HasForeignKey("CategoryId") + .HasConstraintName("CategoryIssueTickets") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("CodeClinic.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("CodeClinic.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CodeClinic.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("CodeClinic.Infrastructure.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.cs b/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.cs new file mode 100644 index 0000000..5738fdb --- /dev/null +++ b/src/Infrastructure/Persistence/Migrations/20201119120107_ChangedReviewToComment.cs @@ -0,0 +1,80 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace CodeClinic.Infrastructure.Persistence.Migrations +{ + public partial class ChangedReviewToComment : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Reviews"); + + migrationBuilder.CreateTable( + name: "Comments", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + CreatedBy = table.Column(nullable: true), + Created = table.Column(nullable: false), + LastModifiedBy = table.Column(nullable: true), + LastModified = table.Column(nullable: true), + IssueTicketId = table.Column(nullable: false), + Title = table.Column(nullable: true), + Description = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Comments", x => x.Id); + table.ForeignKey( + name: "FK_Comments_IssueTickets_IssueTicketId", + column: x => x.IssueTicketId, + principalTable: "IssueTickets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Comments_IssueTicketId", + table: "Comments", + column: "IssueTicketId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Comments"); + + migrationBuilder.CreateTable( + name: "Reviews", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: false), + IssueTicketId = table.Column(type: "int", nullable: false), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + Title = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Reviews", x => x.Id); + table.ForeignKey( + name: "FK_Reviews_IssueTickets_IssueTicketId", + column: x => x.IssueTicketId, + principalTable: "IssueTickets", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Reviews_IssueTicketId", + table: "Reviews", + column: "IssueTicketId"); + } + } +} diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 0f28a74..ecbffd2 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -52,83 +52,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Categories"); }); - modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => + modelBuilder.Entity("CodeClinic.Domain.Entities.Comment", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Body") - .HasColumnType("nvarchar(max)"); - - b.Property("CategoryId") - .HasColumnType("int"); - b.Property("Created") .HasColumnType("datetime2"); b.Property("CreatedBy") .HasColumnType("nvarchar(max)"); + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IssueTicketId") + .HasColumnType("int"); + b.Property("LastModified") .HasColumnType("datetime2"); b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); - b.Property("Stars") - .HasColumnType("int"); - - b.Property("Status") - .HasColumnType("int"); - b.Property("Title") - .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasColumnType("nvarchar(max)"); b.HasKey("Id"); - b.HasIndex("CategoryId"); + b.HasIndex("IssueTicketId"); - b.ToTable("IssueTickets"); + b.ToTable("Comments"); }); - modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => + modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + b.Property("Body") + .HasColumnType("nvarchar(max)"); + + b.Property("CategoryId") + .HasColumnType("int"); + b.Property("Created") .HasColumnType("datetime2"); b.Property("CreatedBy") .HasColumnType("nvarchar(max)"); - b.Property("Description") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("IssueTicketId") - .HasColumnType("int"); - b.Property("LastModified") .HasColumnType("datetime2"); b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + b.Property("Title") - .HasColumnType("nvarchar(max)"); + .IsRequired() + .HasColumnType("nvarchar(200)") + .HasMaxLength(200); b.HasKey("Id"); - b.HasIndex("IssueTicketId"); + b.HasIndex("CategoryId"); - b.ToTable("Reviews"); + b.ToTable("IssueTickets"); }); modelBuilder.Entity("CodeClinic.Infrastructure.Identity.ApplicationUser", b => @@ -413,6 +413,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("CodeClinic.Domain.Entities.Comment", b => + { + b.HasOne("CodeClinic.Domain.Entities.IssueTicket", "IssueTicket") + .WithMany("Comments") + .HasForeignKey("IssueTicketId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => { b.HasOne("CodeClinic.Domain.Entities.Category", "Category") @@ -423,15 +432,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); - modelBuilder.Entity("CodeClinic.Domain.Entities.Review", b => - { - b.HasOne("CodeClinic.Domain.Entities.IssueTicket", "IssueTicket") - .WithMany("Reviews") - .HasForeignKey("IssueTicketId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) diff --git a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts index 32124e2..73dfc89 100644 --- a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts +++ b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts @@ -619,18 +619,18 @@ export class IssueTicketsClient implements IIssueTicketsClient { } } -export interface IReviewsClient { - getAll(issueTicketId: number): Observable; - create(issueTicketId: number, command: CreateReviewCommand): Observable; - getReviewById(issueTicketId: number, id: number): Observable; - update(id: number, issueTicketId: string, command: UpdateReviewCommand): Observable; +export interface ICommentsClient { + getAll(issueTicketId: number): Observable; + create(issueTicketId: number, command: CreateCommentCommand): Observable; + getCommentById(issueTicketId: number, id: number): Observable; + update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable; delete(issueTicketId: number, id: number): Observable; } @Injectable({ providedIn: 'root' }) -export class ReviewsClient implements IReviewsClient { +export class CommentsClient implements ICommentsClient { private http: HttpClient; private baseUrl: string; protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; @@ -640,8 +640,8 @@ export class ReviewsClient implements IReviewsClient { this.baseUrl = baseUrl ? baseUrl : ""; } - getAll(issueTicketId: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews"; + getAll(issueTicketId: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; if (issueTicketId === undefined || issueTicketId === null) throw new Error("The parameter 'issueTicketId' must be defined."); url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); @@ -662,14 +662,14 @@ export class ReviewsClient implements IReviewsClient { try { return this.processGetAll(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetAll(response: HttpResponseBase): Observable { + protected processGetAll(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -680,7 +680,7 @@ export class ReviewsClient implements IReviewsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = ReviewListVm.fromJS(resultData200); + result200 = CommentListVm.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -688,11 +688,11 @@ export class ReviewsClient implements IReviewsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - create(issueTicketId: number, command: CreateReviewCommand): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews"; + create(issueTicketId: number, command: CreateCommentCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; if (issueTicketId === undefined || issueTicketId === null) throw new Error("The parameter 'issueTicketId' must be defined."); url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); @@ -744,8 +744,8 @@ export class ReviewsClient implements IReviewsClient { return _observableOf(null); } - getReviewById(issueTicketId: number, id: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + getCommentById(issueTicketId: number, id: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; if (issueTicketId === undefined || issueTicketId === null) throw new Error("The parameter 'issueTicketId' must be defined."); url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); @@ -763,20 +763,20 @@ export class ReviewsClient implements IReviewsClient { }; return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => { - return this.processGetReviewById(response_); + return this.processGetCommentById(response_); })).pipe(_observableCatch((response_: any) => { if (response_ instanceof HttpResponseBase) { try { - return this.processGetReviewById(response_); + return this.processGetCommentById(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetReviewById(response: HttpResponseBase): Observable { + protected processGetCommentById(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -787,7 +787,7 @@ export class ReviewsClient implements IReviewsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = ReviewDto.fromJS(resultData200); + result200 = CommentDto.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -795,11 +795,11 @@ export class ReviewsClient implements IReviewsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - update(id: number, issueTicketId: string, command: UpdateReviewCommand): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); @@ -855,7 +855,7 @@ export class ReviewsClient implements IReviewsClient { } delete(issueTicketId: number, id: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Reviews/{id}"; + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; if (issueTicketId === undefined || issueTicketId === null) throw new Error("The parameter 'issueTicketId' must be defined."); url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); @@ -1331,7 +1331,7 @@ export class IssueTicketDetailVm implements IIssueTicketDetailVm { body?: string | undefined; status?: ProgressStatus; categoryName?: string | undefined; - reviews?: ReviewDto[] | undefined; + comments?: CommentDto[] | undefined; constructor(data?: IIssueTicketDetailVm) { if (data) { @@ -1351,10 +1351,10 @@ export class IssueTicketDetailVm implements IIssueTicketDetailVm { this.body = _data["body"]; this.status = _data["status"]; this.categoryName = _data["categoryName"]; - if (Array.isArray(_data["reviews"])) { - this.reviews = [] as any; - for (let item of _data["reviews"]) - this.reviews!.push(ReviewDto.fromJS(item)); + if (Array.isArray(_data["comments"])) { + this.comments = [] as any; + for (let item of _data["comments"]) + this.comments!.push(CommentDto.fromJS(item)); } } } @@ -1375,10 +1375,10 @@ export class IssueTicketDetailVm implements IIssueTicketDetailVm { data["body"] = this.body; data["status"] = this.status; data["categoryName"] = this.categoryName; - if (Array.isArray(this.reviews)) { - data["reviews"] = []; - for (let item of this.reviews) - data["reviews"].push(item.toJSON()); + if (Array.isArray(this.comments)) { + data["comments"] = []; + for (let item of this.comments) + data["comments"].push(item.toJSON()); } return data; } @@ -1392,7 +1392,7 @@ export interface IIssueTicketDetailVm { body?: string | undefined; status?: ProgressStatus; categoryName?: string | undefined; - reviews?: ReviewDto[] | undefined; + comments?: CommentDto[] | undefined; } export enum ProgressStatus { @@ -1401,13 +1401,13 @@ export enum ProgressStatus { Answered = 2, } -export class ReviewDto implements IReviewDto { - reviewId?: number; +export class CommentDto implements ICommentDto { + commentId?: number; issueTicketId?: number; title?: string | undefined; description?: string | undefined; - constructor(data?: IReviewDto) { + constructor(data?: ICommentDto) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -1418,23 +1418,23 @@ export class ReviewDto implements IReviewDto { init(_data?: any) { if (_data) { - this.reviewId = _data["reviewId"]; + this.commentId = _data["commentId"]; this.issueTicketId = _data["issueTicketId"]; this.title = _data["title"]; this.description = _data["description"]; } } - static fromJS(data: any): ReviewDto { + static fromJS(data: any): CommentDto { data = typeof data === 'object' ? data : {}; - let result = new ReviewDto(); + let result = new CommentDto(); result.init(data); return result; } toJSON(data?: any) { data = typeof data === 'object' ? data : {}; - data["reviewId"] = this.reviewId; + data["commentId"] = this.commentId; data["issueTicketId"] = this.issueTicketId; data["title"] = this.title; data["description"] = this.description; @@ -1442,8 +1442,8 @@ export class ReviewDto implements IReviewDto { } } -export interface IReviewDto { - reviewId?: number; +export interface ICommentDto { + commentId?: number; issueTicketId?: number; title?: string | undefined; description?: string | undefined; @@ -1593,10 +1593,10 @@ export interface IUpdateIssueTicketDetailsCommand { body?: string | undefined; } -export class ReviewListVm implements IReviewListVm { - items?: ReviewDto[] | undefined; +export class CommentListVm implements ICommentListVm { + items?: CommentDto[] | undefined; - constructor(data?: IReviewListVm) { + constructor(data?: ICommentListVm) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -1610,14 +1610,14 @@ export class ReviewListVm implements IReviewListVm { if (Array.isArray(_data["items"])) { this.items = [] as any; for (let item of _data["items"]) - this.items!.push(ReviewDto.fromJS(item)); + this.items!.push(CommentDto.fromJS(item)); } } } - static fromJS(data: any): ReviewListVm { + static fromJS(data: any): CommentListVm { data = typeof data === 'object' ? data : {}; - let result = new ReviewListVm(); + let result = new CommentListVm(); result.init(data); return result; } @@ -1633,16 +1633,16 @@ export class ReviewListVm implements IReviewListVm { } } -export interface IReviewListVm { - items?: ReviewDto[] | undefined; +export interface ICommentListVm { + items?: CommentDto[] | undefined; } -export class CreateReviewCommand implements ICreateReviewCommand { +export class CreateCommentCommand implements ICreateCommentCommand { issueTicketId?: number; title?: string | undefined; description?: string | undefined; - constructor(data?: ICreateReviewCommand) { + constructor(data?: ICreateCommentCommand) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -1659,9 +1659,9 @@ export class CreateReviewCommand implements ICreateReviewCommand { } } - static fromJS(data: any): CreateReviewCommand { + static fromJS(data: any): CreateCommentCommand { data = typeof data === 'object' ? data : {}; - let result = new CreateReviewCommand(); + let result = new CreateCommentCommand(); result.init(data); return result; } @@ -1675,19 +1675,19 @@ export class CreateReviewCommand implements ICreateReviewCommand { } } -export interface ICreateReviewCommand { +export interface ICreateCommentCommand { issueTicketId?: number; title?: string | undefined; description?: string | undefined; } -export class UpdateReviewCommand implements IUpdateReviewCommand { +export class UpdateCommentCommand implements IUpdateCommentCommand { issueTicketId?: number; - reviewId?: number; + commentId?: number; title?: string | undefined; description?: string | undefined; - constructor(data?: IUpdateReviewCommand) { + constructor(data?: IUpdateCommentCommand) { if (data) { for (var property in data) { if (data.hasOwnProperty(property)) @@ -1699,15 +1699,15 @@ export class UpdateReviewCommand implements IUpdateReviewCommand { init(_data?: any) { if (_data) { this.issueTicketId = _data["issueTicketId"]; - this.reviewId = _data["reviewId"]; + this.commentId = _data["commentId"]; this.title = _data["title"]; this.description = _data["description"]; } } - static fromJS(data: any): UpdateReviewCommand { + static fromJS(data: any): UpdateCommentCommand { data = typeof data === 'object' ? data : {}; - let result = new UpdateReviewCommand(); + let result = new UpdateCommentCommand(); result.init(data); return result; } @@ -1715,16 +1715,16 @@ export class UpdateReviewCommand implements IUpdateReviewCommand { toJSON(data?: any) { data = typeof data === 'object' ? data : {}; data["issueTicketId"] = this.issueTicketId; - data["reviewId"] = this.reviewId; + data["commentId"] = this.commentId; data["title"] = this.title; data["description"] = this.description; return data; } } -export interface IUpdateReviewCommand { +export interface IUpdateCommentCommand { issueTicketId?: number; - reviewId?: number; + commentId?: number; title?: string | undefined; description?: string | undefined; } diff --git a/src/WebUI/Controllers/ReviewsController.cs b/src/WebUI/Controllers/ReviewsController.cs index 967b2c1..a08f44d 100644 --- a/src/WebUI/Controllers/ReviewsController.cs +++ b/src/WebUI/Controllers/ReviewsController.cs @@ -1,33 +1,33 @@ using System.Security.Cryptography; -using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Application.Comments.Query.GetCommentList; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; -using CodeClinic.Application.Reviews.Commands.CreateReview; -using CodeClinic.Application.Reviews.Commands.UpdateReview; -using CodeClinic.Application.Reviews.Commands.DeleteReview; -using CodeClinic.Application.Reviews.Query.GetReviewDetails; +using CodeClinic.Application.Comments.Commands.CreateComment; +using CodeClinic.Application.Comments.Commands.UpdateComment; +using CodeClinic.Application.Comments.Commands.DeleteComment; +using CodeClinic.Application.Comments.Query.GetCommentDetails; namespace CodeClinic.WebUI.Controllers { [Route("api/issueTickets/{issueTicketId}/[controller]")] - public class ReviewsController : ApiController + public class CommentsController : ApiController { [HttpGet] - public async Task> GetAll(int issueTicketId) + public async Task> GetAll(int issueTicketId) { - return await Mediator.Send(new GetReviewListQuery(issueTicketId)); + return await Mediator.Send(new GetCommentListQuery(issueTicketId)); } [HttpGet("{id}")] - public async Task> GetReviewById(int issueTicketId,int id) + public async Task> GetCommentById(int issueTicketId,int id) { - return await Mediator.Send(new GetReviewDetailsQuery { IssueTicketId = issueTicketId,ReviewId = id }); + return await Mediator.Send(new GetCommentDetailsQuery { IssueTicketId = issueTicketId,CommentId = id }); } [HttpPost] - public async Task Create(int issueTicketId, CreateReviewCommand command) + public async Task Create(int issueTicketId, CreateCommentCommand command) { if (issueTicketId != command.IssueTicketId) return BadRequest(); @@ -36,7 +36,7 @@ public async Task Create(int issueTicketId, CreateReviewCommand co [HttpPut("{id}")] - public async Task Update(int id, UpdateReviewCommand command) + public async Task Update(int id, UpdateCommentCommand command) { await Mediator.Send(command); return NoContent(); @@ -45,7 +45,7 @@ public async Task Update(int id, UpdateReviewCommand command) [HttpDelete("{id}")] public async Task Delete(int issueTicketId,int id) { - await Mediator.Send(new DeleteReviewCommand {IssueTicketId = issueTicketId, Id = id}); + await Mediator.Send(new DeleteCommentCommand {IssueTicketId = issueTicketId, Id = id}); return NoContent(); } } diff --git a/src/WebUI/wwwroot/api/specification.json b/src/WebUI/wwwroot/api/specification.json index 10e089c..cab2c03 100644 --- a/src/WebUI/wwwroot/api/specification.json +++ b/src/WebUI/wwwroot/api/specification.json @@ -398,12 +398,12 @@ ] } }, - "/api/issueTickets/{issueTicketId}/Reviews": { + "/api/issueTickets/{issueTicketId}/Comments": { "get": { "tags": [ - "Reviews" + "Comments" ], - "operationId": "Reviews_GetAll", + "operationId": "Comments_GetAll", "parameters": [ { "name": "issueTicketId", @@ -422,7 +422,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReviewListVm" + "$ref": "#/components/schemas/CommentListVm" } } } @@ -431,9 +431,9 @@ }, "post": { "tags": [ - "Reviews" + "Comments" ], - "operationId": "Reviews_Create", + "operationId": "Comments_Create", "parameters": [ { "name": "issueTicketId", @@ -451,7 +451,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateReviewCommand" + "$ref": "#/components/schemas/CreateCommentCommand" } } }, @@ -473,12 +473,12 @@ } } }, - "/api/issueTickets/{issueTicketId}/Reviews/{id}": { + "/api/issueTickets/{issueTicketId}/Comments/{id}": { "get": { "tags": [ - "Reviews" + "Comments" ], - "operationId": "Reviews_GetReviewById", + "operationId": "Comments_GetCommentById", "parameters": [ { "name": "issueTicketId", @@ -507,7 +507,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ReviewDto" + "$ref": "#/components/schemas/CommentDto" } } } @@ -516,9 +516,9 @@ }, "put": { "tags": [ - "Reviews" + "Comments" ], - "operationId": "Reviews_Update", + "operationId": "Comments_Update", "parameters": [ { "name": "id", @@ -545,7 +545,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateReviewCommand" + "$ref": "#/components/schemas/UpdateCommentCommand" } } }, @@ -568,9 +568,9 @@ }, "delete": { "tags": [ - "Reviews" + "Comments" ], - "operationId": "Reviews_Delete", + "operationId": "Comments_Delete", "parameters": [ { "name": "issueTicketId", @@ -832,11 +832,11 @@ "type": "string", "nullable": true }, - "reviews": { + "comments": { "type": "array", "nullable": true, "items": { - "$ref": "#/components/schemas/ReviewDto" + "$ref": "#/components/schemas/CommentDto" } } } @@ -855,11 +855,11 @@ 2 ] }, - "ReviewDto": { + "CommentDto": { "type": "object", "additionalProperties": false, "properties": { - "reviewId": { + "commentId": { "type": "integer", "format": "int32" }, @@ -942,7 +942,7 @@ } } }, - "ReviewListVm": { + "CommentListVm": { "type": "object", "additionalProperties": false, "properties": { @@ -950,12 +950,12 @@ "type": "array", "nullable": true, "items": { - "$ref": "#/components/schemas/ReviewDto" + "$ref": "#/components/schemas/CommentDto" } } } }, - "CreateReviewCommand": { + "CreateCommentCommand": { "type": "object", "additionalProperties": false, "properties": { @@ -973,7 +973,7 @@ } } }, - "UpdateReviewCommand": { + "UpdateCommentCommand": { "type": "object", "additionalProperties": false, "properties": { @@ -981,7 +981,7 @@ "type": "integer", "format": "int32" }, - "reviewId": { + "commentId": { "type": "integer", "format": "int32" }, diff --git a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs b/tests/Application.UnitTests/Common/Mappings/MappingTests.cs index 01bb74d..3d2b177 100644 --- a/tests/Application.UnitTests/Common/Mappings/MappingTests.cs +++ b/tests/Application.UnitTests/Common/Mappings/MappingTests.cs @@ -2,7 +2,7 @@ using CodeClinic.Application.Categories.Queries.GetCategoryList; using CodeClinic.Application.Common.Mappings; using CodeClinic.Application.Issues.Queries.GetIssueList; -using CodeClinic.Application.Reviews.Query.GetReviewList; +using CodeClinic.Application.Comments.Query.GetCommentList; using CodeClinic.Domain.Entities; using NUnit.Framework; using System; @@ -33,7 +33,7 @@ public void ShouldHaveValidConfiguration() [Test] [TestCase(typeof(IssueTicket), typeof(IssueTicketDto))] [TestCase(typeof(Category), typeof(CategoryDto))] - [TestCase(typeof(Review), typeof(ReviewDto))] + [TestCase(typeof(Comment), typeof(CommentDto))] public void ShouldSupportMappingFromSourceToDestination(Type source, Type destination) { var instance = Activator.CreateInstance(source); From 1a176ff265557ec4b9fb0e6c7010a1345826f9d6 Mon Sep 17 00:00:00 2001 From: NICOLAS MALULEKE Date: Thu, 19 Nov 2020 14:25:13 +0200 Subject: [PATCH 03/11] Renamed file names from review to Comment --- .../Commands/CommentCommandBaseHandler.cs} | 0 .../Commands/CreateComment/CreateCommentCommand.cs} | 0 .../Commands/CreateComment/CreateCommentCommandValidator.cs} | 0 .../Commands/DeleteComment/DeleteCommentCommand.cs} | 0 .../Commands/DeleteComment/DeleteCommentCommandValidator.cs} | 0 .../Commands/UpdateComment/CreateCommentCommandValidator.cs} | 0 .../Commands/UpdateComment/UpdateCommentCommand.cs} | 0 .../Query/GetCommentDetails/GetCommentDetailsQuery.cs} | 0 .../ReviewDto.cs => Comments/Query/GetCommentList/CommentDto.cs} | 0 .../Query/GetCommentList/CommentListVm.cs} | 0 .../Query/GetCommentList/GetCommentListQuery.cs} | 0 src/Domain/Entities/{Review.cs => Comment.cs} | 0 .../Controllers/{ReviewsController.cs => CommentsController.cs} | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename src/Application/{Reviews/Commands/ReviewCommandBaseHandler.cs => Comments/Commands/CommentCommandBaseHandler.cs} (100%) rename src/Application/{Reviews/Commands/CreateReview/CreateReviewCommand.cs => Comments/Commands/CreateComment/CreateCommentCommand.cs} (100%) rename src/Application/{Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs => Comments/Commands/CreateComment/CreateCommentCommandValidator.cs} (100%) rename src/Application/{Reviews/Commands/DeleteReview/DeleteReviewCommand.cs => Comments/Commands/DeleteComment/DeleteCommentCommand.cs} (100%) rename src/Application/{Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs => Comments/Commands/DeleteComment/DeleteCommentCommandValidator.cs} (100%) rename src/Application/{Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs => Comments/Commands/UpdateComment/CreateCommentCommandValidator.cs} (100%) rename src/Application/{Reviews/Commands/UpdateReview/UpdateReviewCommand.cs => Comments/Commands/UpdateComment/UpdateCommentCommand.cs} (100%) rename src/Application/{Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs => Comments/Query/GetCommentDetails/GetCommentDetailsQuery.cs} (100%) rename src/Application/{Reviews/Query/GetReviewList/ReviewDto.cs => Comments/Query/GetCommentList/CommentDto.cs} (100%) rename src/Application/{Reviews/Query/GetReviewList/ReviewListVm.cs => Comments/Query/GetCommentList/CommentListVm.cs} (100%) rename src/Application/{Reviews/Query/GetReviewList/GetReviewListQuery.cs => Comments/Query/GetCommentList/GetCommentListQuery.cs} (100%) rename src/Domain/Entities/{Review.cs => Comment.cs} (100%) rename src/WebUI/Controllers/{ReviewsController.cs => CommentsController.cs} (100%) diff --git a/src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs b/src/Application/Comments/Commands/CommentCommandBaseHandler.cs similarity index 100% rename from src/Application/Reviews/Commands/ReviewCommandBaseHandler.cs rename to src/Application/Comments/Commands/CommentCommandBaseHandler.cs diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs b/src/Application/Comments/Commands/CreateComment/CreateCommentCommand.cs similarity index 100% rename from src/Application/Reviews/Commands/CreateReview/CreateReviewCommand.cs rename to src/Application/Comments/Commands/CreateComment/CreateCommentCommand.cs diff --git a/src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs b/src/Application/Comments/Commands/CreateComment/CreateCommentCommandValidator.cs similarity index 100% rename from src/Application/Reviews/Commands/CreateReview/CreateReviewCommandValidator.cs rename to src/Application/Comments/Commands/CreateComment/CreateCommentCommandValidator.cs diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs b/src/Application/Comments/Commands/DeleteComment/DeleteCommentCommand.cs similarity index 100% rename from src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommand.cs rename to src/Application/Comments/Commands/DeleteComment/DeleteCommentCommand.cs diff --git a/src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs b/src/Application/Comments/Commands/DeleteComment/DeleteCommentCommandValidator.cs similarity index 100% rename from src/Application/Reviews/Commands/DeleteReview/DeleteReviewCommandValidator.cs rename to src/Application/Comments/Commands/DeleteComment/DeleteCommentCommandValidator.cs diff --git a/src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs b/src/Application/Comments/Commands/UpdateComment/CreateCommentCommandValidator.cs similarity index 100% rename from src/Application/Reviews/Commands/UpdateReview/CreateReviewCommandValidator.cs rename to src/Application/Comments/Commands/UpdateComment/CreateCommentCommandValidator.cs diff --git a/src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs b/src/Application/Comments/Commands/UpdateComment/UpdateCommentCommand.cs similarity index 100% rename from src/Application/Reviews/Commands/UpdateReview/UpdateReviewCommand.cs rename to src/Application/Comments/Commands/UpdateComment/UpdateCommentCommand.cs diff --git a/src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs b/src/Application/Comments/Query/GetCommentDetails/GetCommentDetailsQuery.cs similarity index 100% rename from src/Application/Reviews/Query/GetReviewDetails/GetReviewDetailsQuery.cs rename to src/Application/Comments/Query/GetCommentDetails/GetCommentDetailsQuery.cs diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewDto.cs b/src/Application/Comments/Query/GetCommentList/CommentDto.cs similarity index 100% rename from src/Application/Reviews/Query/GetReviewList/ReviewDto.cs rename to src/Application/Comments/Query/GetCommentList/CommentDto.cs diff --git a/src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs b/src/Application/Comments/Query/GetCommentList/CommentListVm.cs similarity index 100% rename from src/Application/Reviews/Query/GetReviewList/ReviewListVm.cs rename to src/Application/Comments/Query/GetCommentList/CommentListVm.cs diff --git a/src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs b/src/Application/Comments/Query/GetCommentList/GetCommentListQuery.cs similarity index 100% rename from src/Application/Reviews/Query/GetReviewList/GetReviewListQuery.cs rename to src/Application/Comments/Query/GetCommentList/GetCommentListQuery.cs diff --git a/src/Domain/Entities/Review.cs b/src/Domain/Entities/Comment.cs similarity index 100% rename from src/Domain/Entities/Review.cs rename to src/Domain/Entities/Comment.cs diff --git a/src/WebUI/Controllers/ReviewsController.cs b/src/WebUI/Controllers/CommentsController.cs similarity index 100% rename from src/WebUI/Controllers/ReviewsController.cs rename to src/WebUI/Controllers/CommentsController.cs From e09c4166962af1fc1107ffb280a64d426551dba2 Mon Sep 17 00:00:00 2001 From: Nicolas <36539498+hnicolus@users.noreply.github.com> Date: Thu, 19 Nov 2020 23:34:58 +0200 Subject: [PATCH 04/11] Delete BuildAndTestMain.yml --- .github/workflows/BuildAndTestMain.yml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .github/workflows/BuildAndTestMain.yml diff --git a/.github/workflows/BuildAndTestMain.yml b/.github/workflows/BuildAndTestMain.yml deleted file mode 100644 index 509c2ed..0000000 --- a/.github/workflows/BuildAndTestMain.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.301 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal From 38f122a8edbc229f8c78220e2799f3433b971ece Mon Sep 17 00:00:00 2001 From: NICOLAS MALULEKE Date: Thu, 19 Nov 2020 23:57:53 +0200 Subject: [PATCH 05/11] Framewrk updated to dotnet 5.0 --- src/Application/Application.csproj | 4 +- src/Domain/Domain.csproj | 2 +- src/Infrastructure/Infrastructure.csproj | 12 +- src/WebUI/ClientApp/src/app/CodeClinic-api.ts | 674 +++++++++--------- src/WebUI/WebUI.csproj | 14 +- src/WebUI/wwwroot/api/specification.json | 458 ++++++------ .../Application.UnitTests.csproj | 10 +- .../Common/CommandTestBase.cs | 22 + .../Common/ContextFactory.cs | 16 + .../Commands/CreateIssueTicketTests.cs | 11 + .../Application.IntegrationTests.csproj | 4 +- .../Domain.UnitTests/Domain.UnitTests.csproj | 4 +- 12 files changed, 643 insertions(+), 588 deletions(-) create mode 100644 tests/Application.UnitTests/Common/CommandTestBase.cs create mode 100644 tests/Application.UnitTests/Common/ContextFactory.cs create mode 100644 tests/Application.UnitTests/IssueTickets/Commands/CreateIssueTicketTests.cs diff --git a/src/Application/Application.csproj b/src/Application/Application.csproj index 86bbe3a..35b215e 100644 --- a/src/Application/Application.csproj +++ b/src/Application/Application.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net5.0 CodeClinic.Application CodeClinic.Application @@ -12,7 +12,7 @@ - + diff --git a/src/Domain/Domain.csproj b/src/Domain/Domain.csproj index 65629b5..f7bcc82 100644 --- a/src/Domain/Domain.csproj +++ b/src/Domain/Domain.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net5.0 CodeClinic.Domain CodeClinic.Domain diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 29ae36b..44f235f 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -1,17 +1,17 @@  - netcoreapp3.1 + net5.0 CodeClinic.Infrastructure CodeClinic.Infrastructure - - - - - + + + + + diff --git a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts index 73dfc89..e0650b6 100644 --- a/src/WebUI/ClientApp/src/app/CodeClinic-api.ts +++ b/src/WebUI/ClientApp/src/app/CodeClinic-api.ts @@ -289,19 +289,18 @@ export class CategoriesClient implements ICategoriesClient { } } -export interface IIssueTicketsClient { - getAll(): Observable; - create(command: CreateIssueTicketCommand): Observable; - getIssueTicketById(id: number): Observable; - update(id: number, command: UpdateIssueTicketCommand): Observable; - delete(id: number): Observable; - updateDetails(id: number | undefined, command: UpdateIssueTicketDetailsCommand): Observable; +export interface ICommentsClient { + getAll(issueTicketId: number): Observable; + create(issueTicketId: number, command: CreateCommentCommand): Observable; + getCommentById(issueTicketId: number, id: number): Observable; + update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable; + delete(issueTicketId: number, id: number): Observable; } @Injectable({ providedIn: 'root' }) -export class IssueTicketsClient implements IIssueTicketsClient { +export class CommentsClient implements ICommentsClient { private http: HttpClient; private baseUrl: string; protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; @@ -311,8 +310,11 @@ export class IssueTicketsClient implements IIssueTicketsClient { this.baseUrl = baseUrl ? baseUrl : ""; } - getAll(): Observable { - let url_ = this.baseUrl + "/api/IssueTickets"; + getAll(issueTicketId: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); url_ = url_.replace(/[?&]$/, ""); let options_ : any = { @@ -330,14 +332,14 @@ export class IssueTicketsClient implements IIssueTicketsClient { try { return this.processGetAll(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetAll(response: HttpResponseBase): Observable { + protected processGetAll(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -348,7 +350,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = IssueTicketListVm.fromJS(resultData200); + result200 = CommentListVm.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -356,11 +358,14 @@ export class IssueTicketsClient implements IIssueTicketsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - create(command: CreateIssueTicketCommand): Observable { - let url_ = this.baseUrl + "/api/IssueTickets"; + create(issueTicketId: number, command: CreateCommentCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(command); @@ -371,7 +376,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { responseType: "blob", headers: new HttpHeaders({ "Content-Type": "application/json", - "Accept": "application/json" + "Accept": "application/octet-stream" }) }; @@ -382,37 +387,38 @@ export class IssueTicketsClient implements IIssueTicketsClient { try { return this.processCreate(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processCreate(response: HttpResponseBase): Observable { + protected processCreate(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : (response).error instanceof Blob ? (response).error : undefined; let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; - if (status === 200) { - return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { - let result200: any = null; - let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = resultData200 !== undefined ? resultData200 : null; - return _observableOf(result200); - })); + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); } else if (status !== 200 && status !== 204) { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - getIssueTicketById(id: number): Observable { - let url_ = this.baseUrl + "/api/IssueTickets/{id}"; + getCommentById(issueTicketId: number, id: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); @@ -427,20 +433,20 @@ export class IssueTicketsClient implements IIssueTicketsClient { }; return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => { - return this.processGetIssueTicketById(response_); + return this.processGetCommentById(response_); })).pipe(_observableCatch((response_: any) => { if (response_ instanceof HttpResponseBase) { try { - return this.processGetIssueTicketById(response_); + return this.processGetCommentById(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetIssueTicketById(response: HttpResponseBase): Observable { + protected processGetCommentById(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -451,7 +457,7 @@ export class IssueTicketsClient implements IIssueTicketsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = IssueTicketDetailVm.fromJS(resultData200); + result200 = CommentDto.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -459,14 +465,17 @@ export class IssueTicketsClient implements IIssueTicketsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - update(id: number, command: UpdateIssueTicketCommand): Observable { - let url_ = this.baseUrl + "/api/IssueTickets/{id}"; + update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(command); @@ -515,8 +524,11 @@ export class IssueTicketsClient implements IIssueTicketsClient { return _observableOf(null); } - delete(id: number): Observable { - let url_ = this.baseUrl + "/api/IssueTickets/{id}"; + delete(issueTicketId: number, id: number): Observable { + let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; + if (issueTicketId === undefined || issueTicketId === null) + throw new Error("The parameter 'issueTicketId' must be defined."); + url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); @@ -563,74 +575,21 @@ export class IssueTicketsClient implements IIssueTicketsClient { } return _observableOf(null); } - - updateDetails(id: number | undefined, command: UpdateIssueTicketDetailsCommand): Observable { - let url_ = this.baseUrl + "/api/IssueTickets/UpdateDetails?"; - if (id === null) - throw new Error("The parameter 'id' cannot be null."); - else if (id !== undefined) - url_ += "id=" + encodeURIComponent("" + id) + "&"; - url_ = url_.replace(/[?&]$/, ""); - - const content_ = JSON.stringify(command); - - let options_ : any = { - body: content_, - observe: "response", - responseType: "blob", - headers: new HttpHeaders({ - "Content-Type": "application/json", - "Accept": "application/octet-stream" - }) - }; - - return this.http.request("put", url_, options_).pipe(_observableMergeMap((response_ : any) => { - return this.processUpdateDetails(response_); - })).pipe(_observableCatch((response_: any) => { - if (response_ instanceof HttpResponseBase) { - try { - return this.processUpdateDetails(response_); - } catch (e) { - return >_observableThrow(e); - } - } else - return >_observableThrow(response_); - })); - } - - protected processUpdateDetails(response: HttpResponseBase): Observable { - const status = response.status; - const responseBlob = - response instanceof HttpResponse ? response.body : - (response).error instanceof Blob ? (response).error : undefined; - - let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; - if (status === 200 || status === 206) { - const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; - const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; - const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; - return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); - } else if (status !== 200 && status !== 204) { - return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { - return throwException("An unexpected server error occurred.", status, _responseText, _headers); - })); - } - return _observableOf(null); - } } -export interface ICommentsClient { - getAll(issueTicketId: number): Observable; - create(issueTicketId: number, command: CreateCommentCommand): Observable; - getCommentById(issueTicketId: number, id: number): Observable; - update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable; - delete(issueTicketId: number, id: number): Observable; +export interface IIssueTicketsClient { + getAll(): Observable; + create(command: CreateIssueTicketCommand): Observable; + getIssueTicketById(id: number): Observable; + update(id: number, command: UpdateIssueTicketCommand): Observable; + delete(id: number): Observable; + updateDetails(id: number | undefined, command: UpdateIssueTicketDetailsCommand): Observable; } @Injectable({ providedIn: 'root' }) -export class CommentsClient implements ICommentsClient { +export class IssueTicketsClient implements IIssueTicketsClient { private http: HttpClient; private baseUrl: string; protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; @@ -640,11 +599,8 @@ export class CommentsClient implements ICommentsClient { this.baseUrl = baseUrl ? baseUrl : ""; } - getAll(issueTicketId: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; - if (issueTicketId === undefined || issueTicketId === null) - throw new Error("The parameter 'issueTicketId' must be defined."); - url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + getAll(): Observable { + let url_ = this.baseUrl + "/api/IssueTickets"; url_ = url_.replace(/[?&]$/, ""); let options_ : any = { @@ -662,14 +618,14 @@ export class CommentsClient implements ICommentsClient { try { return this.processGetAll(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetAll(response: HttpResponseBase): Observable { + protected processGetAll(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -680,7 +636,7 @@ export class CommentsClient implements ICommentsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = CommentListVm.fromJS(resultData200); + result200 = IssueTicketListVm.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -688,14 +644,11 @@ export class CommentsClient implements ICommentsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - create(issueTicketId: number, command: CreateCommentCommand): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments"; - if (issueTicketId === undefined || issueTicketId === null) - throw new Error("The parameter 'issueTicketId' must be defined."); - url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + create(command: CreateIssueTicketCommand): Observable { + let url_ = this.baseUrl + "/api/IssueTickets"; url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(command); @@ -706,7 +659,7 @@ export class CommentsClient implements ICommentsClient { responseType: "blob", headers: new HttpHeaders({ "Content-Type": "application/json", - "Accept": "application/octet-stream" + "Accept": "application/json" }) }; @@ -717,38 +670,37 @@ export class CommentsClient implements ICommentsClient { try { return this.processCreate(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processCreate(response: HttpResponseBase): Observable { + protected processCreate(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : (response).error instanceof Blob ? (response).error : undefined; let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; - if (status === 200 || status === 206) { - const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; - const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; - const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; - return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); + if (status === 200) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + return _observableOf(result200); + })); } else if (status !== 200 && status !== 204) { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - getCommentById(issueTicketId: number, id: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; - if (issueTicketId === undefined || issueTicketId === null) - throw new Error("The parameter 'issueTicketId' must be defined."); - url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + getIssueTicketById(id: number): Observable { + let url_ = this.baseUrl + "/api/IssueTickets/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); @@ -763,20 +715,20 @@ export class CommentsClient implements ICommentsClient { }; return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_ : any) => { - return this.processGetCommentById(response_); + return this.processGetIssueTicketById(response_); })).pipe(_observableCatch((response_: any) => { if (response_ instanceof HttpResponseBase) { try { - return this.processGetCommentById(response_); + return this.processGetIssueTicketById(response_); } catch (e) { - return >_observableThrow(e); + return >_observableThrow(e); } } else - return >_observableThrow(response_); + return >_observableThrow(response_); })); } - protected processGetCommentById(response: HttpResponseBase): Observable { + protected processGetIssueTicketById(response: HttpResponseBase): Observable { const status = response.status; const responseBlob = response instanceof HttpResponse ? response.body : @@ -787,7 +739,7 @@ export class CommentsClient implements ICommentsClient { return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { let result200: any = null; let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); - result200 = CommentDto.fromJS(resultData200); + result200 = IssueTicketDetailVm.fromJS(resultData200); return _observableOf(result200); })); } else if (status !== 200 && status !== 204) { @@ -795,17 +747,14 @@ export class CommentsClient implements ICommentsClient { return throwException("An unexpected server error occurred.", status, _responseText, _headers); })); } - return _observableOf(null); + return _observableOf(null); } - update(id: number, issueTicketId: string, command: UpdateCommentCommand): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; + update(id: number, command: UpdateIssueTicketCommand): Observable { + let url_ = this.baseUrl + "/api/IssueTickets/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); - if (issueTicketId === undefined || issueTicketId === null) - throw new Error("The parameter 'issueTicketId' must be defined."); - url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); url_ = url_.replace(/[?&]$/, ""); const content_ = JSON.stringify(command); @@ -854,11 +803,8 @@ export class CommentsClient implements ICommentsClient { return _observableOf(null); } - delete(issueTicketId: number, id: number): Observable { - let url_ = this.baseUrl + "/api/issueTickets/{issueTicketId}/Comments/{id}"; - if (issueTicketId === undefined || issueTicketId === null) - throw new Error("The parameter 'issueTicketId' must be defined."); - url_ = url_.replace("{issueTicketId}", encodeURIComponent("" + issueTicketId)); + delete(id: number): Observable { + let url_ = this.baseUrl + "/api/IssueTickets/{id}"; if (id === undefined || id === null) throw new Error("The parameter 'id' must be defined."); url_ = url_.replace("{id}", encodeURIComponent("" + id)); @@ -905,10 +851,64 @@ export class CommentsClient implements ICommentsClient { } return _observableOf(null); } -} -export class CategoryListVm implements ICategoryListVm { - categories?: CategoryDto[] | undefined; + updateDetails(id: number | undefined, command: UpdateIssueTicketDetailsCommand): Observable { + let url_ = this.baseUrl + "/api/IssueTickets/UpdateDetails?"; + if (id === null) + throw new Error("The parameter 'id' cannot be null."); + else if (id !== undefined) + url_ += "id=" + encodeURIComponent("" + id) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = JSON.stringify(command); + + let options_ : any = { + body: content_, + observe: "response", + responseType: "blob", + headers: new HttpHeaders({ + "Content-Type": "application/json", + "Accept": "application/octet-stream" + }) + }; + + return this.http.request("put", url_, options_).pipe(_observableMergeMap((response_ : any) => { + return this.processUpdateDetails(response_); + })).pipe(_observableCatch((response_: any) => { + if (response_ instanceof HttpResponseBase) { + try { + return this.processUpdateDetails(response_); + } catch (e) { + return >_observableThrow(e); + } + } else + return >_observableThrow(response_); + })); + } + + protected processUpdateDetails(response: HttpResponseBase): Observable { + const status = response.status; + const responseBlob = + response instanceof HttpResponse ? response.body : + (response).error instanceof Blob ? (response).error : undefined; + + let _headers: any = {}; if (response.headers) { for (let key of response.headers.keys()) { _headers[key] = response.headers.get(key); }}; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + const fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + const fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + return _observableOf({ fileName: fileName, data: responseBlob, status: status, headers: _headers }); + } else if (status !== 200 && status !== 204) { + return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + })); + } + return _observableOf(null); + } +} + +export class CategoryListVm implements ICategoryListVm { + categories?: CategoryDto[] | undefined; constructor(data?: ICategoryListVm) { if (data) { @@ -1227,6 +1227,190 @@ export interface IUpdateCategoryCommand { description?: string | undefined; } +export class CommentListVm implements ICommentListVm { + items?: CommentDto[] | undefined; + + constructor(data?: ICommentListVm) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + if (Array.isArray(_data["items"])) { + this.items = [] as any; + for (let item of _data["items"]) + this.items!.push(CommentDto.fromJS(item)); + } + } + } + + static fromJS(data: any): CommentListVm { + data = typeof data === 'object' ? data : {}; + let result = new CommentListVm(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + if (Array.isArray(this.items)) { + data["items"] = []; + for (let item of this.items) + data["items"].push(item.toJSON()); + } + return data; + } +} + +export interface ICommentListVm { + items?: CommentDto[] | undefined; +} + +export class CommentDto implements ICommentDto { + commentId?: number; + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: ICommentDto) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.commentId = _data["commentId"]; + this.issueTicketId = _data["issueTicketId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): CommentDto { + data = typeof data === 'object' ? data : {}; + let result = new CommentDto(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["commentId"] = this.commentId; + data["issueTicketId"] = this.issueTicketId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface ICommentDto { + commentId?: number; + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; +} + +export class CreateCommentCommand implements ICreateCommentCommand { + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: ICreateCommentCommand) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.issueTicketId = _data["issueTicketId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): CreateCommentCommand { + data = typeof data === 'object' ? data : {}; + let result = new CreateCommentCommand(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["issueTicketId"] = this.issueTicketId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface ICreateCommentCommand { + issueTicketId?: number; + title?: string | undefined; + description?: string | undefined; +} + +export class UpdateCommentCommand implements IUpdateCommentCommand { + issueTicketId?: number; + commentId?: number; + title?: string | undefined; + description?: string | undefined; + + constructor(data?: IUpdateCommentCommand) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.issueTicketId = _data["issueTicketId"]; + this.commentId = _data["commentId"]; + this.title = _data["title"]; + this.description = _data["description"]; + } + } + + static fromJS(data: any): UpdateCommentCommand { + data = typeof data === 'object' ? data : {}; + let result = new UpdateCommentCommand(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["issueTicketId"] = this.issueTicketId; + data["commentId"] = this.commentId; + data["title"] = this.title; + data["description"] = this.description; + return data; + } +} + +export interface IUpdateCommentCommand { + issueTicketId?: number; + commentId?: number; + title?: string | undefined; + description?: string | undefined; +} + export class IssueTicketListVm implements IIssueTicketListVm { progressStatuses?: ProgressStatusDto[] | undefined; issues?: IssueTicketDto[] | undefined; @@ -1401,54 +1585,6 @@ export enum ProgressStatus { Answered = 2, } -export class CommentDto implements ICommentDto { - commentId?: number; - issueTicketId?: number; - title?: string | undefined; - description?: string | undefined; - - constructor(data?: ICommentDto) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - this.commentId = _data["commentId"]; - this.issueTicketId = _data["issueTicketId"]; - this.title = _data["title"]; - this.description = _data["description"]; - } - } - - static fromJS(data: any): CommentDto { - data = typeof data === 'object' ? data : {}; - let result = new CommentDto(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["commentId"] = this.commentId; - data["issueTicketId"] = this.issueTicketId; - data["title"] = this.title; - data["description"] = this.description; - return data; - } -} - -export interface ICommentDto { - commentId?: number; - issueTicketId?: number; - title?: string | undefined; - description?: string | undefined; -} - export class CreateIssueTicketCommand implements ICreateIssueTicketCommand { id?: number; title?: string | undefined; @@ -1593,142 +1729,6 @@ export interface IUpdateIssueTicketDetailsCommand { body?: string | undefined; } -export class CommentListVm implements ICommentListVm { - items?: CommentDto[] | undefined; - - constructor(data?: ICommentListVm) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - if (Array.isArray(_data["items"])) { - this.items = [] as any; - for (let item of _data["items"]) - this.items!.push(CommentDto.fromJS(item)); - } - } - } - - static fromJS(data: any): CommentListVm { - data = typeof data === 'object' ? data : {}; - let result = new CommentListVm(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - if (Array.isArray(this.items)) { - data["items"] = []; - for (let item of this.items) - data["items"].push(item.toJSON()); - } - return data; - } -} - -export interface ICommentListVm { - items?: CommentDto[] | undefined; -} - -export class CreateCommentCommand implements ICreateCommentCommand { - issueTicketId?: number; - title?: string | undefined; - description?: string | undefined; - - constructor(data?: ICreateCommentCommand) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - this.issueTicketId = _data["issueTicketId"]; - this.title = _data["title"]; - this.description = _data["description"]; - } - } - - static fromJS(data: any): CreateCommentCommand { - data = typeof data === 'object' ? data : {}; - let result = new CreateCommentCommand(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["issueTicketId"] = this.issueTicketId; - data["title"] = this.title; - data["description"] = this.description; - return data; - } -} - -export interface ICreateCommentCommand { - issueTicketId?: number; - title?: string | undefined; - description?: string | undefined; -} - -export class UpdateCommentCommand implements IUpdateCommentCommand { - issueTicketId?: number; - commentId?: number; - title?: string | undefined; - description?: string | undefined; - - constructor(data?: IUpdateCommentCommand) { - if (data) { - for (var property in data) { - if (data.hasOwnProperty(property)) - (this)[property] = (data)[property]; - } - } - } - - init(_data?: any) { - if (_data) { - this.issueTicketId = _data["issueTicketId"]; - this.commentId = _data["commentId"]; - this.title = _data["title"]; - this.description = _data["description"]; - } - } - - static fromJS(data: any): UpdateCommentCommand { - data = typeof data === 'object' ? data : {}; - let result = new UpdateCommentCommand(); - result.init(data); - return result; - } - - toJSON(data?: any) { - data = typeof data === 'object' ? data : {}; - data["issueTicketId"] = this.issueTicketId; - data["commentId"] = this.commentId; - data["title"] = this.title; - data["description"] = this.description; - return data; - } -} - -export interface IUpdateCommentCommand { - issueTicketId?: number; - commentId?: number; - title?: string | undefined; - description?: string | undefined; -} - export interface FileResponse { data: Blob; status: number; diff --git a/src/WebUI/WebUI.csproj b/src/WebUI/WebUI.csproj index 2faa811..623652a 100644 --- a/src/WebUI/WebUI.csproj +++ b/src/WebUI/WebUI.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 CodeClinic.WebUI CodeClinic.WebUI true @@ -17,12 +17,12 @@ - - - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/WebUI/wwwroot/api/specification.json b/src/WebUI/wwwroot/api/specification.json index cab2c03..de0e23d 100644 --- a/src/WebUI/wwwroot/api/specification.json +++ b/src/WebUI/wwwroot/api/specification.json @@ -167,76 +167,90 @@ } } }, - "/api/IssueTickets": { + "/api/issueTickets/{issueTicketId}/Comments": { "get": { "tags": [ - "IssueTickets" + "Comments" + ], + "operationId": "Comments_GetAll", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } ], - "operationId": "IssueTickets_GetAll", "responses": { "200": { "description": "", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssueTicketListVm" + "$ref": "#/components/schemas/CommentListVm" } } } } - }, - "security": [ - { - "JWT": [] - } - ] + } }, "post": { "tags": [ - "IssueTickets" + "Comments" + ], + "operationId": "Comments_Create", + "parameters": [ + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 1 + } ], - "operationId": "IssueTickets_Create", "requestBody": { "x-name": "command", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateIssueTicketCommand" + "$ref": "#/components/schemas/CreateCommentCommand" } } }, "required": true, - "x-position": 1 + "x-position": 2 }, "responses": { "200": { "description": "", "content": { - "application/json": { + "application/octet-stream": { "schema": { - "type": "integer", - "format": "int32" + "type": "string", + "format": "binary" } } } } - }, - "security": [ - { - "JWT": [] - } - ] + } } }, - "/api/IssueTickets/{id}": { + "/api/issueTickets/{issueTicketId}/Comments/{id}": { "get": { "tags": [ - "IssueTickets" + "Comments" ], - "operationId": "IssueTickets_GetIssueTicketById", + "operationId": "Comments_GetCommentById", "parameters": [ { - "name": "id", + "name": "issueTicketId", "in": "path", "required": true, "schema": { @@ -244,6 +258,16 @@ "format": "int32" }, "x-position": 1 + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 2 } ], "responses": { @@ -252,23 +276,18 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/IssueTicketDetailVm" + "$ref": "#/components/schemas/CommentDto" } } } } - }, - "security": [ - { - "JWT": [] - } - ] + } }, "put": { "tags": [ - "IssueTickets" + "Comments" ], - "operationId": "IssueTickets_Update", + "operationId": "Comments_Update", "parameters": [ { "name": "id", @@ -279,6 +298,15 @@ "format": "int32" }, "x-position": 1 + }, + { + "name": "issueTicketId", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "x-position": 3 } ], "requestBody": { @@ -286,7 +314,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateIssueTicketCommand" + "$ref": "#/components/schemas/UpdateCommentCommand" } } }, @@ -305,21 +333,16 @@ } } } - }, - "security": [ - { - "JWT": [] - } - ] + } }, "delete": { "tags": [ - "IssueTickets" + "Comments" ], - "operationId": "IssueTickets_Delete", + "operationId": "Comments_Delete", "parameters": [ { - "name": "id", + "name": "issueTicketId", "in": "path", "required": true, "schema": { @@ -327,6 +350,16 @@ "format": "int32" }, "x-position": 1 + }, + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + }, + "x-position": 2 } ], "responses": { @@ -341,51 +374,58 @@ } } } + } + } + }, + "/api/IssueTickets": { + "get": { + "tags": [ + "IssueTickets" + ], + "operationId": "IssueTickets_GetAll", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IssueTicketListVm" + } + } + } + } }, "security": [ { "JWT": [] } ] - } - }, - "/api/IssueTickets/UpdateDetails": { - "put": { + }, + "post": { "tags": [ "IssueTickets" ], - "operationId": "IssueTickets_UpdateDetails", - "parameters": [ - { - "name": "id", - "in": "query", - "schema": { - "type": "integer", - "format": "int32" - }, - "x-position": 1 - } - ], + "operationId": "IssueTickets_Create", "requestBody": { "x-name": "command", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateIssueTicketDetailsCommand" + "$ref": "#/components/schemas/CreateIssueTicketCommand" } } }, "required": true, - "x-position": 2 + "x-position": 1 }, "responses": { "200": { "description": "", "content": { - "application/octet-stream": { + "application/json": { "schema": { - "type": "string", - "format": "binary" + "type": "integer", + "format": "int32" } } } @@ -398,15 +438,15 @@ ] } }, - "/api/issueTickets/{issueTicketId}/Comments": { + "/api/IssueTickets/{id}": { "get": { "tags": [ - "Comments" + "IssueTickets" ], - "operationId": "Comments_GetAll", + "operationId": "IssueTickets_GetIssueTicketById", "parameters": [ { - "name": "issueTicketId", + "name": "id", "in": "path", "required": true, "schema": { @@ -422,21 +462,26 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CommentListVm" + "$ref": "#/components/schemas/IssueTicketDetailVm" } } } } - } + }, + "security": [ + { + "JWT": [] + } + ] }, - "post": { + "put": { "tags": [ - "Comments" + "IssueTickets" ], - "operationId": "Comments_Create", + "operationId": "IssueTickets_Update", "parameters": [ { - "name": "issueTicketId", + "name": "id", "in": "path", "required": true, "schema": { @@ -451,7 +496,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateCommentCommand" + "$ref": "#/components/schemas/UpdateIssueTicketCommand" } } }, @@ -470,26 +515,19 @@ } } } - } - } - }, - "/api/issueTickets/{issueTicketId}/Comments/{id}": { - "get": { + }, + "security": [ + { + "JWT": [] + } + ] + }, + "delete": { "tags": [ - "Comments" + "IssueTickets" ], - "operationId": "Comments_GetCommentById", + "operationId": "IssueTickets_Delete", "parameters": [ - { - "name": "issueTicketId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - }, - "x-position": 1 - }, { "name": "id", "in": "path", @@ -498,46 +536,44 @@ "type": "integer", "format": "int32" }, - "x-position": 2 + "x-position": 1 } ], "responses": { "200": { "description": "", "content": { - "application/json": { + "application/octet-stream": { "schema": { - "$ref": "#/components/schemas/CommentDto" + "type": "string", + "format": "binary" } } } } - } - }, + }, + "security": [ + { + "JWT": [] + } + ] + } + }, + "/api/IssueTickets/UpdateDetails": { "put": { "tags": [ - "Comments" + "IssueTickets" ], - "operationId": "Comments_Update", + "operationId": "IssueTickets_UpdateDetails", "parameters": [ { "name": "id", - "in": "path", - "required": true, + "in": "query", "schema": { "type": "integer", "format": "int32" }, "x-position": 1 - }, - { - "name": "issueTicketId", - "in": "path", - "required": true, - "schema": { - "type": "string" - }, - "x-position": 3 } ], "requestBody": { @@ -545,7 +581,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateCommentCommand" + "$ref": "#/components/schemas/UpdateIssueTicketDetailsCommand" } } }, @@ -564,48 +600,12 @@ } } } - } - }, - "delete": { - "tags": [ - "Comments" - ], - "operationId": "Comments_Delete", - "parameters": [ - { - "name": "issueTicketId", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - }, - "x-position": 1 - }, + }, + "security": [ { - "name": "id", - "in": "path", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - }, - "x-position": 2 - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/octet-stream": { - "schema": { - "type": "string", - "format": "binary" - } - } - } + "JWT": [] } - } + ] } } }, @@ -767,6 +767,81 @@ } } }, + "CommentListVm": { + "type": "object", + "additionalProperties": false, + "properties": { + "items": { + "type": "array", + "nullable": true, + "items": { + "$ref": "#/components/schemas/CommentDto" + } + } + } + }, + "CommentDto": { + "type": "object", + "additionalProperties": false, + "properties": { + "commentId": { + "type": "integer", + "format": "int32" + }, + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } + }, + "CreateCommentCommand": { + "type": "object", + "additionalProperties": false, + "properties": { + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } + }, + "UpdateCommentCommand": { + "type": "object", + "additionalProperties": false, + "properties": { + "issueTicketId": { + "type": "integer", + "format": "int32" + }, + "commentId": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + } + }, "IssueTicketListVm": { "type": "object", "additionalProperties": false, @@ -855,28 +930,6 @@ 2 ] }, - "CommentDto": { - "type": "object", - "additionalProperties": false, - "properties": { - "commentId": { - "type": "integer", - "format": "int32" - }, - "issueTicketId": { - "type": "integer", - "format": "int32" - }, - "title": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string", - "nullable": true - } - } - }, "CreateIssueTicketCommand": { "type": "object", "additionalProperties": false, @@ -941,59 +994,6 @@ "nullable": true } } - }, - "CommentListVm": { - "type": "object", - "additionalProperties": false, - "properties": { - "items": { - "type": "array", - "nullable": true, - "items": { - "$ref": "#/components/schemas/CommentDto" - } - } - } - }, - "CreateCommentCommand": { - "type": "object", - "additionalProperties": false, - "properties": { - "issueTicketId": { - "type": "integer", - "format": "int32" - }, - "title": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string", - "nullable": true - } - } - }, - "UpdateCommentCommand": { - "type": "object", - "additionalProperties": false, - "properties": { - "issueTicketId": { - "type": "integer", - "format": "int32" - }, - "commentId": { - "type": "integer", - "format": "int32" - }, - "title": { - "type": "string", - "nullable": true - }, - "description": { - "type": "string", - "nullable": true - } - } } }, "securitySchemes": { diff --git a/tests/Application.UnitTests/Application.UnitTests.csproj b/tests/Application.UnitTests/Application.UnitTests.csproj index 295f84c..96d5c09 100644 --- a/tests/Application.UnitTests/Application.UnitTests.csproj +++ b/tests/Application.UnitTests/Application.UnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 CodeClinic.Application.UnitTests CodeClinic.Application.UnitTests @@ -9,7 +9,9 @@ - + + + @@ -21,4 +23,8 @@ + + + + diff --git a/tests/Application.UnitTests/Common/CommandTestBase.cs b/tests/Application.UnitTests/Common/CommandTestBase.cs new file mode 100644 index 0000000..25f9c10 --- /dev/null +++ b/tests/Application.UnitTests/Common/CommandTestBase.cs @@ -0,0 +1,22 @@ +using CodeClinic.Infrastructure.Persistence; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.UnitTests.Common +{ + class CommandTestBase : IDisposable + { + + + public CommandTestBase() + { + //_context = ContextFactory.Create(); + } + + public void Dispose() + { + //ContextFactory.Destroy(_context); + } + } +} diff --git a/tests/Application.UnitTests/Common/ContextFactory.cs b/tests/Application.UnitTests/Common/ContextFactory.cs new file mode 100644 index 0000000..a15fd3d --- /dev/null +++ b/tests/Application.UnitTests/Common/ContextFactory.cs @@ -0,0 +1,16 @@ +using CodeClinic.Infrastructure.Persistence; +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.UnitTests.Common +{ + public class ContextFactory + { + public ContextFactory() + { + //var options = new DbContextBuilder(); + + } + } +} diff --git a/tests/Application.UnitTests/IssueTickets/Commands/CreateIssueTicketTests.cs b/tests/Application.UnitTests/IssueTickets/Commands/CreateIssueTicketTests.cs new file mode 100644 index 0000000..f9f6521 --- /dev/null +++ b/tests/Application.UnitTests/IssueTickets/Commands/CreateIssueTicketTests.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CodeClinic.Application.UnitTests.IssueTickets.Commands +{ + public class CreateIssueTicketTests + { + + } +} diff --git a/tests/Applicaton.IntegrationTests/Application.IntegrationTests.csproj b/tests/Applicaton.IntegrationTests/Application.IntegrationTests.csproj index 9072882..26701bd 100644 --- a/tests/Applicaton.IntegrationTests/Application.IntegrationTests.csproj +++ b/tests/Applicaton.IntegrationTests/Application.IntegrationTests.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 CodeClinic.Application.IntegrationTests CodeClinic.Application.IntegrationTests @@ -19,7 +19,7 @@ - + diff --git a/tests/Domain.UnitTests/Domain.UnitTests.csproj b/tests/Domain.UnitTests/Domain.UnitTests.csproj index 99eb743..a89b71a 100644 --- a/tests/Domain.UnitTests/Domain.UnitTests.csproj +++ b/tests/Domain.UnitTests/Domain.UnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net5.0 CodeClinic.Domain.UnitTests CodeClinic.Domain.UnitTests @@ -9,7 +9,7 @@ - + From 1810a817065e4b4321d3c956c7272e7ef1a6b419 Mon Sep 17 00:00:00 2001 From: NICOLAS MALULEKE Date: Fri, 20 Nov 2020 23:32:51 +0200 Subject: [PATCH 06/11] framework patches and initial presentation design --- .../ApplicationDbContextModelSnapshot.cs | 147 +++--- src/WebUI/ClientApp/angular.json | 3 +- src/WebUI/ClientApp/package-lock.json | 5 + src/WebUI/ClientApp/package.json | 1 + .../login-menu/login-menu.component.html | 10 +- .../login/login.component.html | 11 +- .../ClientApp/src/app/app.component.html | 4 +- src/WebUI/ClientApp/src/app/app.module.ts | 36 +- .../src/app/home/home.component.html | 116 +++-- .../ClientApp/src/app/home/home.component.ts | 35 +- .../src/app/nav-menu/nav-menu.component.css | 18 - .../src/app/nav-menu/nav-menu.component.html | 9 +- .../src/app/nav-menu/nav-menu.component.ts | 13 +- src/WebUI/ClientApp/src/index.html | 14 +- src/WebUI/ClientApp/src/styles.css | 425 +++++++++++++++++- .../Controllers/IssueTicketsController.cs | 10 +- src/WebUI/Startup.cs | 1 - src/WebUI/wwwroot/api/specification.json | 7 +- .../Application.UnitTests.csproj | 1 + .../Common/CommandTestBase.cs | 22 - .../Common/ContextFactory.cs | 16 - .../Commands/CreateIssueTicketTests.cs | 25 +- 22 files changed, 733 insertions(+), 196 deletions(-) delete mode 100644 tests/Application.UnitTests/Common/CommandTestBase.cs delete mode 100644 tests/Application.UnitTests/Common/ContextFactory.cs diff --git a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index ecbffd2..abc7f0c 100644 --- a/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -15,16 +15,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "3.1.0") + .UseIdentityColumns() .HasAnnotation("Relational:MaxIdentifierLength", 128) - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation("ProductVersion", "5.0.0"); modelBuilder.Entity("CodeClinic.Domain.Entities.Category", b => { b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property("Created") .HasColumnType("datetime2"); @@ -33,8 +33,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(max)"); b.Property("Description") - .HasColumnType("nvarchar(500)") - .HasMaxLength(500); + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); b.Property("LastModified") .HasColumnType("datetime2"); @@ -44,8 +44,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Name") .IsRequired() - .HasColumnType("nvarchar(30)") - .HasMaxLength(30); + .HasMaxLength(30) + .HasColumnType("nvarchar(30)"); b.HasKey("Id"); @@ -57,7 +57,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property("Created") .HasColumnType("datetime2"); @@ -93,7 +93,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property("Body") .HasColumnType("nvarchar(max)"); @@ -121,8 +121,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Title") .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.HasKey("Id"); @@ -144,8 +144,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(max)"); b.Property("Email") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.Property("EmailConfirmed") .HasColumnType("bit"); @@ -157,12 +157,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("datetimeoffset"); b.Property("NormalizedEmail") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.Property("NormalizedUserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.Property("PasswordHash") .HasColumnType("nvarchar(max)"); @@ -180,17 +180,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bit"); b.Property("UserName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.HasKey("Id"); b.HasIndex("NormalizedEmail") - .HasName("EmailIndex"); + .HasDatabaseName("EmailIndex"); b.HasIndex("NormalizedUserName") .IsUnique() - .HasName("UserNameIndex") + .HasDatabaseName("UserNameIndex") .HasFilter("[NormalizedUserName] IS NOT NULL"); b.ToTable("AspNetUsers"); @@ -199,34 +199,42 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { b.Property("UserCode") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("CreationTime") .HasColumnType("datetime2"); b.Property("Data") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("DeviceCode") .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("Expiration") .IsRequired() .HasColumnType("datetime2"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + b.Property("SubjectId") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.HasKey("UserCode"); @@ -241,33 +249,44 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => { b.Property("Key") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("ClientId") .IsRequired() - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ConsumedTime") + .HasColumnType("datetime2"); b.Property("CreationTime") .HasColumnType("datetime2"); b.Property("Data") .IsRequired() - .HasColumnType("nvarchar(max)") - .HasMaxLength(50000); + .HasMaxLength(50000) + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("Expiration") .HasColumnType("datetime2"); + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + b.Property("SubjectId") - .HasColumnType("nvarchar(200)") - .HasMaxLength(200); + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); b.Property("Type") .IsRequired() - .HasColumnType("nvarchar(50)") - .HasMaxLength(50); + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); b.HasKey("Key"); @@ -275,6 +294,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("SubjectId", "ClientId", "Type"); + b.HasIndex("SubjectId", "SessionId", "Type"); + b.ToTable("PersistedGrants"); }); @@ -288,18 +309,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(max)"); b.Property("Name") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.Property("NormalizedName") - .HasColumnType("nvarchar(256)") - .HasMaxLength(256); + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); b.HasKey("Id"); b.HasIndex("NormalizedName") .IsUnique() - .HasName("RoleNameIndex") + .HasDatabaseName("RoleNameIndex") .HasFilter("[NormalizedName] IS NOT NULL"); b.ToTable("AspNetRoles"); @@ -310,7 +331,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -334,7 +355,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Id") .ValueGeneratedOnAdd() .HasColumnType("int") - .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property("ClaimType") .HasColumnType("nvarchar(max)"); @@ -356,12 +377,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => { b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("ProviderKey") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("ProviderDisplayName") .HasColumnType("nvarchar(max)"); @@ -398,12 +419,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(450)"); b.Property("LoginProvider") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("Name") - .HasColumnType("nvarchar(128)") - .HasMaxLength(128); + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); b.Property("Value") .HasColumnType("nvarchar(max)"); @@ -420,6 +441,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasForeignKey("IssueTicketId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); + + b.Navigation("IssueTicket"); }); modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => @@ -430,6 +453,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasConstraintName("CategoryIssueTickets") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); + + b.Navigation("Category"); }); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => @@ -482,6 +507,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.Category", b => + { + b.Navigation("IssueTickets"); + }); + + modelBuilder.Entity("CodeClinic.Domain.Entities.IssueTicket", b => + { + b.Navigation("Comments"); + }); #pragma warning restore 612, 618 } } diff --git a/src/WebUI/ClientApp/angular.json b/src/WebUI/ClientApp/angular.json index a1205f7..d73c2cd 100644 --- a/src/WebUI/ClientApp/angular.json +++ b/src/WebUI/ClientApp/angular.json @@ -25,9 +25,8 @@ "src/assets" ], "styles": [ - "./node_modules/bootstrap/dist/css/bootstrap.min.css", + "./node_modules/bootswatch/dist/materia/bootstrap.min.css", "./node_modules/ngx-bootstrap/datepicker/bs-datepicker.css", - "src/styles.css" ], "scripts": [ diff --git a/src/WebUI/ClientApp/package-lock.json b/src/WebUI/ClientApp/package-lock.json index 11a1492..0f0f1cb 100644 --- a/src/WebUI/ClientApp/package-lock.json +++ b/src/WebUI/ClientApp/package-lock.json @@ -3751,6 +3751,11 @@ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==" }, + "bootswatch": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/bootswatch/-/bootswatch-4.5.3.tgz", + "integrity": "sha512-gaB3gBSAegmYbk97aVELKcSKVdPjWsSY4yCITkUt/SqbqjtMU/HtIUszb4O9vzdbrfuVXThc/qCXzjoJaAPgiQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/src/WebUI/ClientApp/package.json b/src/WebUI/ClientApp/package.json index 4fb4be5..f1b1be0 100644 --- a/src/WebUI/ClientApp/package.json +++ b/src/WebUI/ClientApp/package.json @@ -27,6 +27,7 @@ "@nguniversal/module-map-ngfactory-loader": "8.0.0-rc.1", "aspnet-prerendering": "^3.0.1", "bootstrap": "^4.3.1", + "bootswatch": "^4.5.3", "core-js": "^2.6.5", "datatables.net-bs4": "^1.10.22", "jquery": "3.5.0", diff --git a/src/WebUI/ClientApp/src/api-authorization/login-menu/login-menu.component.html b/src/WebUI/ClientApp/src/api-authorization/login-menu/login-menu.component.html index 30fa656..e20934d 100644 --- a/src/WebUI/ClientApp/src/api-authorization/login-menu/login-menu.component.html +++ b/src/WebUI/ClientApp/src/api-authorization/login-menu/login-menu.component.html @@ -1,16 +1,16 @@ + diff --git a/src/WebUI/ClientApp/src/api-authorization/login/login.component.html b/src/WebUI/ClientApp/src/api-authorization/login/login.component.html index 5b5e4e4..b064212 100644 --- a/src/WebUI/ClientApp/src/api-authorization/login/login.component.html +++ b/src/WebUI/ClientApp/src/api-authorization/login/login.component.html @@ -1 +1,10 @@ -

{{ message | async }}

\ No newline at end of file +

{{ message | async }}

+
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/src/WebUI/ClientApp/src/app/app.component.html b/src/WebUI/ClientApp/src/app/app.component.html index 7173845..f71f158 100644 --- a/src/WebUI/ClientApp/src/app/app.component.html +++ b/src/WebUI/ClientApp/src/app/app.component.html @@ -1,6 +1,4 @@ -
- -
+ diff --git a/src/WebUI/ClientApp/src/app/app.module.ts b/src/WebUI/ClientApp/src/app/app.module.ts index 84bcd43..ce2fca1 100644 --- a/src/WebUI/ClientApp/src/app/app.module.ts +++ b/src/WebUI/ClientApp/src/app/app.module.ts @@ -2,7 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; -import { RouterModule } from '@angular/router'; +import { RouterModule, CanActivate } from '@angular/router'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { AppComponent } from './app.component'; @@ -14,13 +14,25 @@ import { AuthorizeInterceptor } from 'src/api-authorization/authorize.intercepto import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ModalModule } from 'ngx-bootstrap/modal'; import { SummaryPipe } from '../pipes/summary/summary.pipe'; +import { LoginFormComponent } from '../api-authorization/login-form/login-form.component'; +import { LoaderComponent } from './components/loader/loader.component'; +import { PostDetailComponent } from './issue-tickets/post-detail/post-detail.component'; +import { NotFoundComponent } from './components/not-found/not-found.component'; +import { PostFormComponent } from './components/post-form/post-form.component'; +import { PostsComponent } from './issue-tickets/posts.component'; @NgModule({ declarations: [ AppComponent, NavMenuComponent, HomeComponent, - SummaryPipe + SummaryPipe, + LoginFormComponent, + PostsComponent, + LoaderComponent, + PostDetailComponent, + NotFoundComponent, + PostFormComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), @@ -29,7 +41,25 @@ import { SummaryPipe } from '../pipes/summary/summary.pipe'; FormsModule, ApiAuthorizationModule, RouterModule.forRoot([ - { path: '', component: HomeComponent, pathMatch: 'full' }, + { + path: '', + component: HomeComponent, + pathMatch: 'full' + }, + { + path: 'login', + component: LoginFormComponent + }, + { + path: 'posts/:title/:issueTicketId', + component: PostDetailComponent, + canActivate: [AuthorizeGuard] + }, + + { + path: '**', + component: NotFoundComponent + }, ]), BrowserAnimationsModule, ModalModule.forRoot() diff --git a/src/WebUI/ClientApp/src/app/home/home.component.html b/src/WebUI/ClientApp/src/app/home/home.component.html index 0916b77..3c115d8 100644 --- a/src/WebUI/ClientApp/src/app/home/home.component.html +++ b/src/WebUI/ClientApp/src/app/home/home.component.html @@ -1,27 +1,93 @@ - +
+
+
+
+
+

Welcome to the Code Clinic

+

+ Don't die of code frustration when devs exist all over the world. + Join a Community of highly Creative individuals who are always willing to help +

+

+ Login + Sign up +

+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+
+
+
+
+
@AdminAdmin
+
Name : Admin clinic
+
About : Developer of web applications, JavaScript, PHP, Java, Python, Ruby, Java, Node.js, etc. +
+
+
    +
  • +
    Followers
    +
    5.2342
    +
  • +
  • +
    Following
    +
    6758
    +
  • +
+
+
+
+
-
- - - - - - - - - - - - - - - - - - - - - -
Issue TicketDescriptionCategoryStarsDate CreatedIssue StatusAction
{{ ticket.title | summary:30 }}{{ ticket.body | summary:60 }}{{ ticket.categoryName }}{{ ticket.stars }}{{ ticket.dateCreated | date:'mediumDate' }}{{ ticket.status }}
+ + + +
+
+
+ +
+
+
+ +
+ +
+ + + + + + diff --git a/src/WebUI/ClientApp/src/app/home/home.component.ts b/src/WebUI/ClientApp/src/app/home/home.component.ts index e7a8e9c..574e6e2 100644 --- a/src/WebUI/ClientApp/src/app/home/home.component.ts +++ b/src/WebUI/ClientApp/src/app/home/home.component.ts @@ -1,28 +1,23 @@ -import { Component, TemplateRef } from '@angular/core'; -import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; -import { IIssueTicketDto, IssueTicketDto, IssueTicketListVm, IssueTicketsClient, ProgressStatus } from '../CodeClinic-api'; - +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { AuthorizeService } from '../../api-authorization/authorize.service'; +import { map } from 'rxjs/operators'; @Component({ selector: 'app-home', templateUrl: './home.component.html', + styleUrls: ['./home.component.css'] }) -export class HomeComponent { - - modalRef : BsModalRef - - issueTicketsListvm: IssueTicketListVm; - issuesTicketList: IIssueTicketDto[]; - - constructor(private client: IssueTicketsClient, private modalService :BsModalService ) { - - this.client.getAll().subscribe(result => { - this.issuesTicketList = result.issues; - - }, error => console.error(error)); - +export class HomeComponent implements OnInit { + + public isAuthenticated: Observable; + public userName: Observable; + + constructor(private authorizeService: AuthorizeService) { } + + ngOnInit() { + this.isAuthenticated = this.authorizeService.isAuthenticated(); + this.userName = this.authorizeService.getUser().pipe(map(u => u && u.name)); } - - } diff --git a/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.css b/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.css index 10389ef..e69de29 100644 --- a/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.css +++ b/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.css @@ -1,18 +0,0 @@ -a.navbar-brand { - white-space: normal; - text-align: center; - word-break: break-all; -} - -html { - font-size: 14px; -} -@media (min-width: 768px) { - html { - font-size: 16px; - } -} - -.box-shadow { - box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); -} diff --git a/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.html b/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.html index 0178be5..5cbcdd3 100644 --- a/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.html +++ b/src/WebUI/ClientApp/src/app/nav-menu/nav-menu.component.html @@ -1,5 +1,5 @@
-