-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/services evaluation #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 2 commits
f8206f1
9454c6c
ece7732
70ec25d
46c0160
2f368db
c72f16c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,9 +93,10 @@ private static async Task WriteValidationResultAsync(HttpContext ctx, Validation | |
|
|
||
| var fieldErrors = ex.Errors.Select(e => | ||
| { | ||
| var domainKey = e.ErrorMessage; | ||
| var domainKey = e.ErrorCode; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explain this change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those were optional fixes I added to improve the ERR900 validation error display. |
||
| var valCode = SystemCodeMap.ToSystemCode(domainKey); | ||
| var valMsg = l?.GetString(domainKey) ?? domainKey; | ||
| if (valMsg == domainKey) valMsg = e.ErrorMessage; | ||
| return new | ||
| { | ||
| field = ToCamelCase(e.PropertyName), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| using CCE.Api.Common.Extensions; | ||
| using CCE.Application.Evaluation.Commands.SubmitEvaluation; | ||
| using CCE.Domain.Evaluation; | ||
| using MediatR; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Routing; | ||
|
|
||
| namespace CCE.Api.External.Endpoints; | ||
|
|
||
| public static class EvaluationEndpoints | ||
| { | ||
| public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app) | ||
| { | ||
| var group = app.MapGroup("/api/evaluations").WithTags("Evaluations"); | ||
|
|
||
| // POST /api/evaluations — public submit (visitors & authenticated users) | ||
| group.MapPost("", async ( | ||
| SubmitEvaluationRequest body, | ||
| IMediator mediator, | ||
| CancellationToken ct) => | ||
| { | ||
| if (!Enum.IsDefined(typeof(EvaluationRating), body.OverallSatisfaction) || body.OverallSatisfaction == 0) | ||
| return Results.BadRequest(new { error = "OverallSatisfaction must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. // the endpoint currently contains business/input validation rules // moving this to validator |
||
|
|
||
| if (!Enum.IsDefined(typeof(EvaluationRating), body.EaseOfUse) || body.EaseOfUse == 0) | ||
| return Results.BadRequest(new { error = "EaseOfUse must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); | ||
|
|
||
| if (!Enum.IsDefined(typeof(EvaluationRating), body.ContentSuitability) || body.ContentSuitability == 0) | ||
| return Results.BadRequest(new { error = "ContentSuitability must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); | ||
|
|
||
| var cmd = new SubmitEvaluationCommand( | ||
| (EvaluationRating)body.OverallSatisfaction, | ||
| (EvaluationRating)body.EaseOfUse, | ||
| (EvaluationRating)body.ContentSuitability, | ||
| body.Feedback); | ||
| var result = await mediator.Send(cmd, ct).ConfigureAwait(false); | ||
| return result.ToHttpResult(StatusCodes.Status201Created); | ||
| }) | ||
| .AllowAnonymous() | ||
| .WithName("SubmitEvaluation"); | ||
|
|
||
| return app; | ||
| } | ||
| } | ||
|
|
||
| public sealed record SubmitEvaluationRequest( | ||
| int OverallSatisfaction, | ||
| int EaseOfUse, | ||
| int ContentSuitability, | ||
| string Feedback); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| using CCE.Api.Common.Extensions; | ||
| using CCE.Application.Evaluation.Queries.GetAllEvaluations; | ||
| using CCE.Application.Evaluation.Queries.GetEvaluationById; | ||
| using CCE.Domain; | ||
| using MediatR; | ||
| using Microsoft.AspNetCore.Builder; | ||
| using Microsoft.AspNetCore.Http; | ||
| using Microsoft.AspNetCore.Routing; | ||
|
|
||
| namespace CCE.Api.Internal.Endpoints; | ||
|
|
||
| public static class EvaluationEndpoints | ||
| { | ||
| public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app) | ||
| { | ||
| var group = app.MapGroup("/api/admin/evaluations").WithTags("Evaluations"); | ||
|
|
||
| // GET /api/admin/evaluations — list all (admin only) | ||
| group.MapGet("", async ( | ||
| IMediator mediator, | ||
| CancellationToken ct) => | ||
| { | ||
| var result = await mediator.Send(new GetAllEvaluationsQuery(), ct).ConfigureAwait(false); | ||
| return result.ToHttpResult(); | ||
| }) | ||
| .RequireAuthorization(Permissions.Survey_ReadAll) | ||
| .WithName("GetAllEvaluations"); | ||
|
|
||
| // GET /api/admin/evaluations/{id} — get by id (admin only) | ||
| group.MapGet("{id:guid}", async ( | ||
| System.Guid id, | ||
| IMediator mediator, | ||
| CancellationToken ct) => | ||
| { | ||
| var result = await mediator.Send(new GetEvaluationByIdQuery(id), ct).ConfigureAwait(false); | ||
| return result.ToHttpResult(); | ||
| }) | ||
| .RequireAuthorization(Permissions.Survey_ReadAll) | ||
| .WithName("GetEvaluationById"); | ||
|
|
||
| return app; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,9 +47,10 @@ public async Task<TResponse> Handle( | |
| { | ||
| var fieldErrors = failures.Select(f => | ||
| { | ||
| var domainKey = f.ErrorMessage; | ||
| var domainKey = f.ErrorCode; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. explain the change |
||
| var valCode = SystemCodeMap.ToSystemCode(domainKey); | ||
| var msg = _l.GetString(domainKey); | ||
| if (msg == domainKey) msg = f.ErrorMessage; | ||
| return new FieldError( | ||
| ToCamelCase(f.PropertyName), | ||
| valCode, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| using CCE.Application.Common; | ||
| using CCE.Domain.Evaluation; | ||
| using MediatR; | ||
|
|
||
| namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; | ||
|
|
||
| public sealed record SubmitEvaluationCommand( | ||
| EvaluationRating OverallSatisfaction, | ||
| EvaluationRating EaseOfUse, | ||
| EvaluationRating ContentSuitability, | ||
| string Feedback) : IRequest<Response<VoidData>>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| using CCE.Application.Common; | ||
| using CCE.Application.Common.Interfaces; | ||
| using CCE.Application.Errors; | ||
| using CCE.Application.Messages; | ||
| using CCE.Domain.Common; | ||
| using DomainEvaluation = CCE.Domain.Evaluation.ServiceEvaluation; | ||
| using MediatR; | ||
|
|
||
| namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; | ||
|
|
||
| public sealed class SubmitEvaluationCommandHandler | ||
| : IRequestHandler<SubmitEvaluationCommand, Response<VoidData>> | ||
| { | ||
| private readonly IEvaluationRepository _repository; | ||
| private readonly ICurrentUserAccessor _currentUser; | ||
| private readonly ISystemClock _clock; | ||
| private readonly MessageFactory _messageFactory; | ||
|
|
||
| public SubmitEvaluationCommandHandler( | ||
| IEvaluationRepository repository, | ||
| ICurrentUserAccessor currentUser, | ||
| ISystemClock clock, | ||
| MessageFactory messageFactory) | ||
| { | ||
| _repository = repository; | ||
| _currentUser = currentUser; | ||
| _clock = clock; | ||
| _messageFactory = messageFactory; | ||
| } | ||
|
|
||
| public async Task<Response<VoidData>> Handle( | ||
| SubmitEvaluationCommand request, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var userId = _currentUser.GetUserId(); | ||
|
|
||
| var evaluation = DomainEvaluation.Submit( | ||
| request.OverallSatisfaction, | ||
| request.EaseOfUse, | ||
| request.ContentSuitability, | ||
| request.Feedback, | ||
| userId, | ||
| _clock); | ||
|
|
||
| await _repository.AddAsync(evaluation, cancellationToken).ConfigureAwait(false); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as i mentioned in faq pr same comment here to use the unit of work to save the transaction |
||
|
|
||
| return _messageFactory.Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| using CCE.Domain.Evaluation; | ||
| using FluentValidation; | ||
|
|
||
| namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; | ||
|
|
||
| public sealed class SubmitEvaluationCommandValidator : AbstractValidator<SubmitEvaluationCommand> | ||
| { | ||
| public SubmitEvaluationCommandValidator() | ||
| { | ||
| RuleFor(x => x.OverallSatisfaction) | ||
| .IsInEnum().WithErrorCode("INVALID_ENUM") | ||
| .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); | ||
| RuleFor(x => x.EaseOfUse) | ||
| .IsInEnum().WithErrorCode("INVALID_ENUM") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. // we can keep only the version with error codes |
||
| .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); | ||
| RuleFor(x => x.ContentSuitability) | ||
| .IsInEnum().WithErrorCode("INVALID_ENUM") | ||
| .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); | ||
| RuleFor(x => x.Feedback) | ||
| .NotEmpty().WithErrorCode("REQUIRED_FIELD") | ||
| .MaximumLength(500).WithErrorCode("MAX_LENGTH"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| using CCE.Domain.Evaluation; | ||
|
|
||
| namespace CCE.Application.Evaluation.DTOs; | ||
|
|
||
| public sealed record ServiceEvaluationDto( | ||
| System.Guid Id, | ||
| EvaluationRating OverallSatisfaction, | ||
| EvaluationRating EaseOfUse, | ||
| EvaluationRating ContentSuitability, | ||
| string Feedback, | ||
| System.Guid? UserId, | ||
| System.DateTimeOffset CreatedOn, | ||
| System.Guid CreatedById); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using CCE.Domain.Evaluation; | ||
|
|
||
| namespace CCE.Application.Evaluation; | ||
|
|
||
| public interface IEvaluationRepository | ||
| { | ||
| Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| using CCE.Application.Common; | ||
| using CCE.Application.Evaluation.DTOs; | ||
| using MediatR; | ||
|
|
||
| namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; | ||
|
|
||
| public sealed record GetAllEvaluationsQuery : IRequest<Response<List<ServiceEvaluationDto>>>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| using CCE.Application.Common; | ||
| using CCE.Application.Common.Interfaces; | ||
| using CCE.Application.Evaluation.DTOs; | ||
| using CCE.Application.Messages; | ||
| using MediatR; | ||
| using Microsoft.EntityFrameworkCore; | ||
|
|
||
| namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; | ||
|
|
||
| public sealed class GetAllEvaluationsQueryHandler | ||
| : IRequestHandler<GetAllEvaluationsQuery, Response<List<ServiceEvaluationDto>>> | ||
| { | ||
| private readonly ICceDbContext _db; | ||
| private readonly MessageFactory _msg; | ||
|
|
||
| public GetAllEvaluationsQueryHandler(ICceDbContext db, MessageFactory msg) | ||
| { | ||
| _db = db; | ||
| _msg = msg; | ||
| } | ||
|
|
||
| public async Task<Response<List<ServiceEvaluationDto>>> Handle( | ||
| GetAllEvaluationsQuery request, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var evaluations = await _db.ServiceEvaluations | ||
| .OrderByDescending(e => e.CreatedOn) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add pagenation to this list |
||
| .ToListAsync(cancellationToken) | ||
| .ConfigureAwait(false); | ||
|
|
||
| var dtos = evaluations.Select(e => new ServiceEvaluationDto( | ||
| e.Id, | ||
| e.OverallSatisfaction, | ||
| e.EaseOfUse, | ||
| e.ContentSuitability, | ||
| e.Feedback, | ||
| e.UserId, | ||
| e.CreatedOn, | ||
| e.CreatedById)).ToList(); | ||
|
|
||
| return _msg.Ok(dtos, "ITEMS_LISTED"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| using CCE.Application.Common; | ||
| using CCE.Application.Evaluation.DTOs; | ||
| using MediatR; | ||
|
|
||
| namespace CCE.Application.Evaluation.Queries.GetEvaluationById; | ||
|
|
||
| public sealed record GetEvaluationByIdQuery(System.Guid Id) : IRequest<Response<ServiceEvaluationDto>>; |
Uh oh!
There was an error while loading. Please reload this page.