Skip to content

Commit f8206f1

Browse files
committed
feat: add service evaluation feature including endpoints, application logic, and migration
1 parent d65c5c5 commit f8206f1

30 files changed

Lines changed: 5737 additions & 0 deletions

backend/AddServiceEvaluation.sql

Lines changed: 1428 additions & 0 deletions
Large diffs are not rendered by default.

backend/src/CCE.Api.Common/Localization/Resources.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,11 @@ NOTIFICATION_TEMPLATE_CREATED:
456456
NOTIFICATION_TEMPLATE_UPDATED:
457457
ar: "تم تحديث قالب الإشعار بنجاح"
458458
en: "Notification template updated successfully"
459+
460+
EVALUATION_NOT_FOUND:
461+
ar: "التقييم غير موجود"
462+
en: "Evaluation not found"
463+
464+
EVALUATION_SUBMITTED:
465+
ar: "تم تقديم التقييم بنجاح"
466+
en: "Evaluation submitted successfully"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.Evaluation.Commands.SubmitEvaluation;
3+
using CCE.Domain.Evaluation;
4+
using MediatR;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Routing;
8+
9+
namespace CCE.Api.External.Endpoints;
10+
11+
public static class EvaluationEndpoints
12+
{
13+
public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app)
14+
{
15+
var group = app.MapGroup("/api/evaluations").WithTags("Evaluations");
16+
17+
// POST /api/evaluations — public submit (visitors & authenticated users)
18+
group.MapPost("", async (
19+
SubmitEvaluationRequest body,
20+
IMediator mediator,
21+
CancellationToken ct) =>
22+
{
23+
var cmd = new SubmitEvaluationCommand(
24+
body.OverallSatisfaction,
25+
body.EaseOfUse,
26+
body.ContentSuitability,
27+
body.Feedback);
28+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
29+
return result.ToHttpResult(StatusCodes.Status201Created);
30+
})
31+
.AllowAnonymous()
32+
.WithName("SubmitEvaluation");
33+
34+
return app;
35+
}
36+
}
37+
38+
public sealed record SubmitEvaluationRequest(
39+
EvaluationRating OverallSatisfaction,
40+
EvaluationRating EaseOfUse,
41+
EvaluationRating ContentSuitability,
42+
string Feedback);

backend/src/CCE.Api.External/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
app.MapAssistantEndpoints();
114114
app.MapKapsarcEndpoints();
115115
app.MapSurveysEndpoints();
116+
app.MapEvaluationEndpoints();
116117
app.MapHomepageSettingsPublicEndpoints();
117118
app.MapAboutSettingsPublicEndpoints();
118119
app.MapPoliciesSettingsPublicEndpoints();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using CCE.Application.Evaluation.Queries.GetAllEvaluations;
2+
using CCE.Application.Evaluation.Queries.GetEvaluationById;
3+
using CCE.Domain;
4+
using MediatR;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.AspNetCore.Routing;
8+
9+
namespace CCE.Api.Internal.Endpoints;
10+
11+
public static class EvaluationEndpoints
12+
{
13+
public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app)
14+
{
15+
var group = app.MapGroup("/api/admin/evaluations").WithTags("Evaluations");
16+
17+
// GET /api/admin/evaluations — list all (admin only)
18+
group.MapGet("", async (
19+
IMediator mediator,
20+
CancellationToken ct) =>
21+
{
22+
var result = await mediator.Send(new GetAllEvaluationsQuery(), ct).ConfigureAwait(false);
23+
return Results.Ok(result);
24+
})
25+
.RequireAuthorization(Permissions.Survey_ReadAll)
26+
.WithName("GetAllEvaluations");
27+
28+
// GET /api/admin/evaluations/{id} — get by id (admin only)
29+
group.MapGet("{id:guid}", async (
30+
System.Guid id,
31+
IMediator mediator,
32+
CancellationToken ct) =>
33+
{
34+
var result = await mediator.Send(new GetEvaluationByIdQuery(id), ct).ConfigureAwait(false);
35+
return result is null ? Results.NotFound() : Results.Ok(result);
36+
})
37+
.RequireAuthorization(Permissions.Survey_ReadAll)
38+
.WithName("GetEvaluationById");
39+
40+
return app;
41+
}
42+
}

backend/src/CCE.Api.Internal/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
app.MapAboutSettingsEndpoints();
8787
app.MapPoliciesSettingsEndpoints();
8888
app.MapMediaEndpoints();
89+
app.MapEvaluationEndpoints();
8990

9091
// Sub-11d follow-up — dev sign-in shim. Mounts /dev/sign-in,
9192
// /dev/sign-out, /dev/whoami when Auth:DevMode=true. Production

backend/src/CCE.Application/Common/Errors.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ public Error RegistrationFailed(IDictionary<string, string[]>? details = null)
5555
public Error PostNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.POST_NOT_FOUND}");
5656
public Error ReplyNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.REPLY_NOT_FOUND}");
5757

58+
// ─── Convenience: Evaluation domain ───
59+
public Error EvaluationNotFound() => NotFound($"EVALUATION_{ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND}");
60+
5861
// ─── Convenience: Country domain ───
5962
public Error CountryNotFound() => NotFound($"COUNTRY_{ApplicationErrors.Country.COUNTRY_NOT_FOUND}");
6063

backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CCE.Domain.Audit;
33
using CCE.Domain.Community;
44
using CCE.Domain.Content;
5+
using CCE.Domain.Evaluation;
56
using CCE.Domain.Identity;
67
using CCE.Domain.InteractiveCity;
78
using CCE.Domain.KnowledgeMaps;
@@ -75,6 +76,9 @@ public interface ICceDbContext
7576
IQueryable<OtpVerification> OtpVerifications { get; }
7677
IQueryable<UserVerification> UserVerifications { get; }
7778

79+
// ─── Evaluation ───
80+
IQueryable<ServiceEvaluation> ServiceEvaluations { get; }
81+
7882
// ─── Media ───
7983
IQueryable<MediaFile> MediaFiles { get; }
8084

backend/src/CCE.Application/Errors/ApplicationErrors.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ public static class InteractiveCity
104104
public const string TECHNOLOGY_NOT_FOUND = "TECHNOLOGY_NOT_FOUND";
105105
}
106106

107+
public static class Evaluation
108+
{
109+
public const string EVALUATION_NOT_FOUND = "EVALUATION_NOT_FOUND";
110+
public const string EVALUATION_SUBMITTED = "EVALUATION_SUBMITTED";
111+
}
112+
107113
public static class Validation
108114
{
109115
public const string REQUIRED_FIELD = "REQUIRED_FIELD";
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using CCE.Application.Common;
2+
using CCE.Domain.Evaluation;
3+
using MediatR;
4+
5+
namespace CCE.Application.Evaluation.Commands.SubmitEvaluation;
6+
7+
public sealed record SubmitEvaluationCommand(
8+
EvaluationRating OverallSatisfaction,
9+
EvaluationRating EaseOfUse,
10+
EvaluationRating ContentSuitability,
11+
string Feedback) : IRequest<Response<VoidData>>;

0 commit comments

Comments
 (0)