Skip to content

Commit 0cddd87

Browse files
authored
Merge pull request #35 from Azm-Tech/feat/services-evaluation
Feat/services evaluation
2 parents 7c5c9e0 + c72f16c commit 0cddd87

32 files changed

Lines changed: 4347 additions & 3 deletions

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ VALIDATION_REQUIRED_FIELD:
162162
ar: "هذا الحقل مطلوب"
163163
en: "This field is required"
164164

165+
REQUIRED_FIELD:
166+
ar: "هذا الحقل مطلوب"
167+
en: "This field is required"
168+
165169
VALIDATION_INVALID_EMAIL:
166170
ar: "البريد الإلكتروني غير صالح"
167171
en: "Invalid email format"
@@ -174,6 +178,10 @@ VALIDATION_MAX_LENGTH:
174178
ar: "القيمة طويلة جدًا"
175179
en: "Value is too long"
176180

181+
MAX_LENGTH:
182+
ar: "القيمة طويلة جدًا"
183+
en: "Value is too long"
184+
177185
VALIDATION_INVALID_FORMAT:
178186
ar: "التنسيق غير صالح"
179187
en: "Invalid format"
@@ -182,6 +190,10 @@ VALIDATION_INVALID_ENUM:
182190
ar: "القيمة المحددة غير صالحة"
183191
en: "Selected value is invalid"
184192

193+
INVALID_ENUM:
194+
ar: "القيمة المحددة غير صالحة"
195+
en: "Selected value is invalid"
196+
185197
# ─── Identity Bare Keys (errors) ───
186198

187199
USER_NOT_FOUND:
@@ -456,3 +468,11 @@ NOTIFICATION_TEMPLATE_CREATED:
456468
NOTIFICATION_TEMPLATE_UPDATED:
457469
ar: "تم تحديث قالب الإشعار بنجاح"
458470
en: "Notification template updated successfully"
471+
472+
EVALUATION_NOT_FOUND:
473+
ar: "التقييم غير موجود"
474+
en: "Evaluation not found"
475+
476+
EVALUATION_SUBMITTED:
477+
ar: "تم تقديم التقييم بنجاح"
478+
en: "Evaluation submitted successfully"

backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ private static async Task WriteValidationResultAsync(HttpContext ctx, Validation
9393

9494
var fieldErrors = ex.Errors.Select(e =>
9595
{
96-
var domainKey = e.ErrorMessage;
96+
var domainKey = e.ErrorCode;
9797
var valCode = SystemCodeMap.ToSystemCode(domainKey);
9898
var valMsg = l?.GetString(domainKey) ?? domainKey;
99+
if (valMsg == domainKey) valMsg = e.ErrorMessage;
99100
return new
100101
{
101102
field = ToCamelCase(e.PropertyName),
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
24+
var cmd = new SubmitEvaluationCommand(
25+
(EvaluationRating)body.OverallSatisfaction,
26+
(EvaluationRating)body.EaseOfUse,
27+
(EvaluationRating)body.ContentSuitability,
28+
body.Feedback);
29+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
30+
return result.ToHttpResult(StatusCodes.Status201Created);
31+
})
32+
.AllowAnonymous()
33+
.WithName("SubmitEvaluation");
34+
35+
return app;
36+
}
37+
}
38+
39+
public sealed record SubmitEvaluationRequest(
40+
int OverallSatisfaction,
41+
int EaseOfUse,
42+
int ContentSuitability,
43+
string Feedback);

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

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
app.MapPoliciesSettingsEndpoints();
8888
app.MapMediaEndpoints();
8989
app.MapCountryCodeEndpoints();
90+
app.MapEvaluationEndpoints();
9091

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

backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,10 @@ public async Task<TResponse> Handle(
4747
{
4848
var fieldErrors = failures.Select(f =>
4949
{
50-
var domainKey = f.ErrorMessage;
50+
var domainKey = f.ErrorCode;
5151
var valCode = SystemCodeMap.ToSystemCode(domainKey);
5252
var msg = _l.GetString(domainKey);
53+
if (msg == domainKey) msg = f.ErrorMessage;
5354
return new FieldError(
5455
ToCamelCase(f.PropertyName),
5556
valCode,

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;
@@ -80,6 +81,9 @@ public interface ICceDbContext
8081
IQueryable<OtpVerification> OtpVerifications { get; }
8182
IQueryable<UserVerification> UserVerifications { get; }
8283

84+
// ─── Evaluation ───
85+
IQueryable<ServiceEvaluation> ServiceEvaluations { get; }
86+
8387
// ─── Media ───
8488
IQueryable<MediaFile> MediaFiles { get; }
8589

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";

0 commit comments

Comments
 (0)