diff --git a/backend/src/CCE.Api.Common/Localization/Resources.yaml b/backend/src/CCE.Api.Common/Localization/Resources.yaml index 3f65608c..f40e5a01 100644 --- a/backend/src/CCE.Api.Common/Localization/Resources.yaml +++ b/backend/src/CCE.Api.Common/Localization/Resources.yaml @@ -162,6 +162,10 @@ VALIDATION_REQUIRED_FIELD: ar: "هذا الحقل مطلوب" en: "This field is required" +REQUIRED_FIELD: + ar: "هذا الحقل مطلوب" + en: "This field is required" + VALIDATION_INVALID_EMAIL: ar: "البريد الإلكتروني غير صالح" en: "Invalid email format" @@ -174,6 +178,10 @@ VALIDATION_MAX_LENGTH: ar: "القيمة طويلة جدًا" en: "Value is too long" +MAX_LENGTH: + ar: "القيمة طويلة جدًا" + en: "Value is too long" + VALIDATION_INVALID_FORMAT: ar: "التنسيق غير صالح" en: "Invalid format" @@ -182,6 +190,10 @@ VALIDATION_INVALID_ENUM: ar: "القيمة المحددة غير صالحة" en: "Selected value is invalid" +INVALID_ENUM: + ar: "القيمة المحددة غير صالحة" + en: "Selected value is invalid" + # ─── Identity Bare Keys (errors) ─── USER_NOT_FOUND: @@ -456,3 +468,11 @@ NOTIFICATION_TEMPLATE_CREATED: NOTIFICATION_TEMPLATE_UPDATED: ar: "تم تحديث قالب الإشعار بنجاح" en: "Notification template updated successfully" + +EVALUATION_NOT_FOUND: + ar: "التقييم غير موجود" + en: "Evaluation not found" + +EVALUATION_SUBMITTED: + ar: "تم تقديم التقييم بنجاح" + en: "Evaluation submitted successfully" diff --git a/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs b/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs index 77a2eb63..354e17b3 100644 --- a/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs +++ b/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs @@ -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; var valCode = SystemCodeMap.ToSystemCode(domainKey); var valMsg = l?.GetString(domainKey) ?? domainKey; + if (valMsg == domainKey) valMsg = e.ErrorMessage; return new { field = ToCamelCase(e.PropertyName), diff --git a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs new file mode 100644 index 00000000..e50670f6 --- /dev/null +++ b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs @@ -0,0 +1,43 @@ +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) => + { + + 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); diff --git a/backend/src/CCE.Api.External/Program.cs b/backend/src/CCE.Api.External/Program.cs index 7e58745c..178a4956 100644 --- a/backend/src/CCE.Api.External/Program.cs +++ b/backend/src/CCE.Api.External/Program.cs @@ -114,6 +114,7 @@ app.MapAssistantEndpoints(); app.MapKapsarcEndpoints(); app.MapSurveysEndpoints(); +app.MapEvaluationEndpoints(); app.MapHomepageSettingsPublicEndpoints(); app.MapAboutSettingsPublicEndpoints(); app.MapPoliciesSettingsPublicEndpoints(); diff --git a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs new file mode 100644 index 00000000..57420849 --- /dev/null +++ b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs @@ -0,0 +1,44 @@ +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 ( + int? page, int? pageSize, + IMediator mediator, + CancellationToken ct) => + { + var result = await mediator.Send(new GetAllEvaluationsQuery(page ?? 1, pageSize ?? 20), 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; + } +} diff --git a/backend/src/CCE.Api.Internal/Program.cs b/backend/src/CCE.Api.Internal/Program.cs index 4d347ef4..1c34eca6 100644 --- a/backend/src/CCE.Api.Internal/Program.cs +++ b/backend/src/CCE.Api.Internal/Program.cs @@ -87,6 +87,7 @@ app.MapPoliciesSettingsEndpoints(); app.MapMediaEndpoints(); app.MapCountryCodeEndpoints(); + app.MapEvaluationEndpoints(); // Sub-11d follow-up — dev sign-in shim. Mounts /dev/sign-in, // /dev/sign-out, /dev/whoami when Auth:DevMode=true. Production diff --git a/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs b/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs index 920459b4..769af6c3 100644 --- a/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs +++ b/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs @@ -47,9 +47,10 @@ public async Task Handle( { var fieldErrors = failures.Select(f => { - var domainKey = f.ErrorMessage; + var domainKey = f.ErrorCode; var valCode = SystemCodeMap.ToSystemCode(domainKey); var msg = _l.GetString(domainKey); + if (msg == domainKey) msg = f.ErrorMessage; return new FieldError( ToCamelCase(f.PropertyName), valCode, diff --git a/backend/src/CCE.Application/Common/Errors.cs b/backend/src/CCE.Application/Common/Errors.cs index 7ed5530b..7b934c5f 100644 --- a/backend/src/CCE.Application/Common/Errors.cs +++ b/backend/src/CCE.Application/Common/Errors.cs @@ -55,6 +55,9 @@ public Error RegistrationFailed(IDictionary? details = null) public Error PostNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.POST_NOT_FOUND}"); public Error ReplyNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.REPLY_NOT_FOUND}"); + // ─── Convenience: Evaluation domain ─── + public Error EvaluationNotFound() => NotFound($"EVALUATION_{ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND}"); + // ─── Convenience: Country domain ─── public Error CountryNotFound() => NotFound($"COUNTRY_{ApplicationErrors.Country.COUNTRY_NOT_FOUND}"); diff --git a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs index 5ca493bc..ccd34ae0 100644 --- a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs +++ b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs @@ -2,6 +2,7 @@ using CCE.Domain.Audit; using CCE.Domain.Community; using CCE.Domain.Content; +using CCE.Domain.Evaluation; using CCE.Domain.Identity; using CCE.Domain.InteractiveCity; using CCE.Domain.KnowledgeMaps; @@ -80,6 +81,9 @@ public interface ICceDbContext IQueryable OtpVerifications { get; } IQueryable UserVerifications { get; } + // ─── Evaluation ─── + IQueryable ServiceEvaluations { get; } + // ─── Media ─── IQueryable MediaFiles { get; } diff --git a/backend/src/CCE.Application/Errors/ApplicationErrors.cs b/backend/src/CCE.Application/Errors/ApplicationErrors.cs index 6fa696d5..27c95fdb 100644 --- a/backend/src/CCE.Application/Errors/ApplicationErrors.cs +++ b/backend/src/CCE.Application/Errors/ApplicationErrors.cs @@ -104,6 +104,12 @@ public static class InteractiveCity public const string TECHNOLOGY_NOT_FOUND = "TECHNOLOGY_NOT_FOUND"; } + public static class Evaluation + { + public const string EVALUATION_NOT_FOUND = "EVALUATION_NOT_FOUND"; + public const string EVALUATION_SUBMITTED = "EVALUATION_SUBMITTED"; + } + public static class Validation { public const string REQUIRED_FIELD = "REQUIRED_FIELD"; diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs new file mode 100644 index 00000000..a3eae52d --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs @@ -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>; diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs new file mode 100644 index 00000000..9e1bc05f --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs @@ -0,0 +1,53 @@ +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> +{ + private readonly IEvaluationRepository _repository; + private readonly ICurrentUserAccessor _currentUser; + private readonly ISystemClock _clock; + private readonly MessageFactory _messageFactory; + private readonly ICceDbContext _db; + + public SubmitEvaluationCommandHandler( + IEvaluationRepository repository, + ICurrentUserAccessor currentUser, + ISystemClock clock, + MessageFactory messageFactory, + ICceDbContext db) + { + _repository = repository; + _currentUser = currentUser; + _clock = clock; + _messageFactory = messageFactory; + _db = db; + } + + public async Task> 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); + await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + + return _messageFactory.Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); + } +} diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs new file mode 100644 index 00000000..9ba6c291 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs @@ -0,0 +1,20 @@ +using CCE.Domain.Evaluation; +using FluentValidation; + +namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; + +public sealed class SubmitEvaluationCommandValidator : AbstractValidator +{ + public SubmitEvaluationCommandValidator() + { + RuleFor(x => x.OverallSatisfaction) + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.EaseOfUse) + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.ContentSuitability) + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.Feedback) + .NotEmpty().WithErrorCode("REQUIRED_FIELD") + .MaximumLength(500).WithErrorCode("MAX_LENGTH"); + } +} diff --git a/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs new file mode 100644 index 00000000..ae99acad --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs @@ -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); diff --git a/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs new file mode 100644 index 00000000..98435889 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs @@ -0,0 +1,8 @@ +using CCE.Domain.Evaluation; + +namespace CCE.Application.Evaluation; + +public interface IEvaluationRepository +{ + Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct); +} diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs new file mode 100644 index 00000000..9186de64 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs @@ -0,0 +1,11 @@ +using CCE.Application.Common; +using CCE.Application.Common.Pagination; +using CCE.Application.Evaluation.DTOs; +using MediatR; + +namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; + +public sealed record GetAllEvaluationsQuery( + int Page = 1, + int PageSize = 20) + : IRequest>>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs new file mode 100644 index 00000000..f8b93aea --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs @@ -0,0 +1,44 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Evaluation.DTOs; +using CCE.Application.Messages; +using MediatR; +using Microsoft.EntityFrameworkCore; +using CCE.Application.Common.Pagination; + +namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; + +public sealed class GetAllEvaluationsQueryHandler + : IRequestHandler>> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public GetAllEvaluationsQueryHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task>> Handle( + GetAllEvaluationsQuery request, + CancellationToken cancellationToken) + { + var query = _db.ServiceEvaluations + .OrderByDescending(e => e.CreatedOn); + var page = await query.ToPagedResultAsync( + request.Page, request.PageSize, cancellationToken) + .ConfigureAwait(false); + var result = page.Map(e => new ServiceEvaluationDto( + e.Id, + e.OverallSatisfaction, + e.EaseOfUse, + e.ContentSuitability, + e.Feedback, + e.UserId, + e.CreatedOn, + e.CreatedById)); + return _msg.Ok(result, "ITEMS_LISTED"); + } +} diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs new file mode 100644 index 00000000..15e77c0f --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs @@ -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>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs new file mode 100644 index 00000000..f8e8f18d --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs @@ -0,0 +1,45 @@ +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.GetEvaluationById; + +public sealed class GetEvaluationByIdQueryHandler + : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public GetEvaluationByIdQueryHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task> Handle( + GetEvaluationByIdQuery request, + CancellationToken cancellationToken) + { + var evaluation = await _db.ServiceEvaluations + .FirstOrDefaultAsync(e => e.Id == request.Id, cancellationToken) + .ConfigureAwait(false); + + if (evaluation is null) + return _msg.EvaluationNotFound(); + + var dto = new ServiceEvaluationDto( + evaluation.Id, + evaluation.OverallSatisfaction, + evaluation.EaseOfUse, + evaluation.ContentSuitability, + evaluation.Feedback, + evaluation.UserId, + evaluation.CreatedOn, + evaluation.CreatedById); + + return _msg.Ok(dto, "ITEMS_LISTED"); + } +} diff --git a/backend/src/CCE.Application/Messages/MessageFactory.cs b/backend/src/CCE.Application/Messages/MessageFactory.cs index 26abf7d6..7c135a2c 100644 --- a/backend/src/CCE.Application/Messages/MessageFactory.cs +++ b/backend/src/CCE.Application/Messages/MessageFactory.cs @@ -1,4 +1,5 @@ using CCE.Application.Common; +using CCE.Application.Errors; using CCE.Application.Localization; using CCE.Domain.Common; @@ -114,6 +115,10 @@ public FieldError Field(string fieldName, string domainKey) public Response EmailUpdated() => Ok("EMAIL_UPDATED"); public Response PhoneUpdated() => Ok("PHONE_UPDATED"); + // ─── Convenience shortcuts (Evaluation domain) ─── + public Response EvaluationSubmitted() => Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); + public Response EvaluationNotFound() => NotFound(ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND); + // ─── Convenience shortcuts (Notification domain) ─── public Response NotificationTemplateNotFound() => NotFound("TEMPLATE_NOT_FOUND"); diff --git a/backend/src/CCE.Application/Messages/SystemCode.cs b/backend/src/CCE.Application/Messages/SystemCode.cs index d9ddfe12..f2b84c57 100644 --- a/backend/src/CCE.Application/Messages/SystemCode.cs +++ b/backend/src/CCE.Application/Messages/SystemCode.cs @@ -115,6 +115,9 @@ public static class SystemCode public const string ERR125 = "ERR125"; // OTP invalidated public const string ERR126 = "ERR126"; // Contact already taken + // ─── Evaluation Errors ─── + public const string ERR009 = "ERR009"; // Evaluation not found + // ─── General Errors ─── public const string ERR900 = "ERR900"; // Internal server error public const string ERR901 = "ERR901"; // Unauthorized access diff --git a/backend/src/CCE.Application/Messages/SystemCodeMap.cs b/backend/src/CCE.Application/Messages/SystemCodeMap.cs index 3bd0323e..a1e1751d 100644 --- a/backend/src/CCE.Application/Messages/SystemCodeMap.cs +++ b/backend/src/CCE.Application/Messages/SystemCodeMap.cs @@ -95,6 +95,9 @@ public static class SystemCodeMap ["OTP_INVALIDATED"] = SystemCode.ERR125, ["CONTACT_ALREADY_TAKEN"] = SystemCode.ERR126, + // ─── Evaluation Errors ─── + ["EVALUATION_NOT_FOUND"] = SystemCode.ERR009, + // ─── General Errors ─── ["INTERNAL_ERROR"] = SystemCode.ERR900, ["UNAUTHORIZED_ACCESS"] = SystemCode.ERR901, @@ -163,6 +166,9 @@ public static class SystemCodeMap ["EMAIL_UPDATED"] = SystemCode.CON062, ["PHONE_UPDATED"] = SystemCode.CON063, + // ─── Evaluation Success ─── + ["EVALUATION_SUBMITTED"] = SystemCode.CON008, + // ─── General Success ─── ["ITEMS_LISTED"] = SystemCode.CON100, ["SUCCESS_OPERATION"] = SystemCode.CON900, diff --git a/backend/src/CCE.Domain/Common/AuditableEntity.cs b/backend/src/CCE.Domain/Common/AuditableEntity.cs index a1ab1f0c..cc355a1e 100644 --- a/backend/src/CCE.Domain/Common/AuditableEntity.cs +++ b/backend/src/CCE.Domain/Common/AuditableEntity.cs @@ -15,7 +15,7 @@ protected AuditableEntity(TId id) : base(id) { } public DateTimeOffset CreatedOn { get; protected set; } /// - public Guid CreatedById { get; protected set; } + public Guid? CreatedById { get; protected set; } /// public DateTimeOffset? LastModifiedOn { get; protected set; } diff --git a/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs b/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs new file mode 100644 index 00000000..785ccb03 --- /dev/null +++ b/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs @@ -0,0 +1,11 @@ +namespace CCE.Domain.Evaluation; + +public enum EvaluationRating +{ + None = 0, + Excellent = 1, + Satisfied = 2, + Neutral = 3, + Dissatisfied = 4, + Poor = 5 +} diff --git a/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs new file mode 100644 index 00000000..bb981eac --- /dev/null +++ b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs @@ -0,0 +1,60 @@ +using CCE.Domain.Common; + +namespace CCE.Domain.Evaluation; + +[Audited] +public sealed class ServiceEvaluation : AuditableEntity +{ + + private ServiceEvaluation( + System.Guid id, + EvaluationRating overallSatisfaction, + EvaluationRating easeOfUse, + EvaluationRating contentSuitability, + string feedback, + System.Guid? userId) : base(id) + { + OverallSatisfaction = overallSatisfaction; + EaseOfUse = easeOfUse; + ContentSuitability = contentSuitability; + Feedback = feedback; + UserId = userId; + } + + public EvaluationRating OverallSatisfaction { get; private set; } + public EvaluationRating EaseOfUse { get; private set; } + public EvaluationRating ContentSuitability { get; private set; } + public string Feedback { get; private set; } + public System.Guid? UserId { get; private set; } + + public static ServiceEvaluation Submit( + EvaluationRating overallSatisfaction, + EvaluationRating easeOfUse, + EvaluationRating contentSuitability, + string feedback, + System.Guid? userId, + ISystemClock clock) + { + if (overallSatisfaction == EvaluationRating.None) + throw new DomainException("OverallSatisfaction is required."); + if (easeOfUse == EvaluationRating.None) + throw new DomainException("EaseOfUse is required."); + if (contentSuitability == EvaluationRating.None) + throw new DomainException("ContentSuitability is required."); + if (string.IsNullOrWhiteSpace(feedback)) + throw new DomainException("Feedback is required."); + + var entity = new ServiceEvaluation( + System.Guid.NewGuid(), + overallSatisfaction, + easeOfUse, + contentSuitability, + feedback.Trim(), + userId); + + entity.CreatedOn = clock.UtcNow; + entity.CreatedById = userId; + + return entity; + } +} diff --git a/backend/src/CCE.Infrastructure/DependencyInjection.cs b/backend/src/CCE.Infrastructure/DependencyInjection.cs index 22fcbca8..1a610727 100644 --- a/backend/src/CCE.Infrastructure/DependencyInjection.cs +++ b/backend/src/CCE.Infrastructure/DependencyInjection.cs @@ -5,6 +5,7 @@ using CCE.Application.Community; using CCE.Application.Content; using CCE.Application.Content.Public; +using CCE.Application.Evaluation; using CCE.Application.Media; using CCE.Application.PlatformSettings; using CCE.Application.Country; @@ -28,6 +29,7 @@ using CCE.Infrastructure.Notifications; using CCE.Infrastructure.Notifications.Messaging; using CCE.Infrastructure.Reports; +using CCE.Infrastructure.Evaluation; using CCE.Infrastructure.Surveys; using CCE.Application.Verification; using CCE.Application.Localization; @@ -224,6 +226,9 @@ public static IServiceCollection AddInfrastructure( // Surveys services.AddScoped(); + // Evaluation + services.AddScoped(); + // Smart assistant — factory routes to stub or Anthropic based on // Assistant:Provider config + ANTHROPIC_API_KEY env-var (Sub-10a). services.AddScoped(); diff --git a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs new file mode 100644 index 00000000..429f2be8 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs @@ -0,0 +1,20 @@ +using CCE.Application.Evaluation; +using CCE.Domain.Evaluation; +using CCE.Infrastructure.Persistence; + +namespace CCE.Infrastructure.Evaluation; + +public sealed class EvaluationRepository : IEvaluationRepository +{ + private readonly CceDbContext _db; + + public EvaluationRepository(CceDbContext db) + { + _db = db; + } + + public async Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct) + { + _db.ServiceEvaluations.Add(evaluation); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs index 6f940c3b..15ff6226 100644 --- a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs +++ b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs @@ -6,6 +6,7 @@ using CCE.Domain.Community; using CCE.Domain.Content; using CCE.Domain.Country; +using CCE.Domain.Evaluation; using CCE.Domain.Identity; using CCE.Domain.InteractiveCity; using CCE.Domain.KnowledgeMaps; @@ -92,6 +93,9 @@ public CceDbContext(DbContextOptions options) : base(options) { } public DbSet ServiceRatings => Set(); public DbSet SearchQueryLogs => Set(); + // ─── Evaluation ─── + public DbSet ServiceEvaluations => Set(); + // ─── Media ─── public DbSet MediaFiles => Set(); @@ -157,6 +161,7 @@ public CceDbContext(DbContextOptions options) : base(options) { } IQueryable ICceDbContext.CountryCodes => CountryCodes.AsNoTracking(); IQueryable ICceDbContext.OtpVerifications => OtpVerifications.AsNoTracking(); IQueryable ICceDbContext.UserVerifications => UserVerifications.AsNoTracking(); + IQueryable ICceDbContext.ServiceEvaluations => ServiceEvaluations.AsNoTracking(); IQueryable ICceDbContext.MediaFiles => MediaFiles.AsNoTracking(); void ICceDbContext.Add(T entity) where T : class => Set().Add(entity); diff --git a/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs b/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs new file mode 100644 index 00000000..de05511c --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs @@ -0,0 +1,35 @@ +using CCE.Domain.Evaluation; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CCE.Infrastructure.Persistence.Configurations.Evaluation; + +internal sealed class ServiceEvaluationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(e => e.Id); + builder.Property(e => e.Id).ValueGeneratedNever(); + + builder.Property(e => e.OverallSatisfaction) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.EaseOfUse) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.ContentSuitability) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.Feedback) + .IsRequired() + .HasMaxLength(500); + + builder.Property(e => e.UserId); + + builder.HasIndex(e => e.CreatedOn) + .HasDatabaseName("ix_service_evaluation_created_on"); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs new file mode 100644 index 00000000..e1d042da --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs @@ -0,0 +1,3758 @@ +// +using System; +using CCE.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CCE.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(CceDbContext))] + [Migration("20260525092623_AddServiceEvaluation")] + partial class AddServiceEvaluation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CCE.Domain.Audit.AuditEvent", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("action"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("actor"); + + b.Property("CorrelationId") + .HasColumnType("uniqueidentifier") + .HasColumnName("correlation_id"); + + b.Property("Diff") + .HasColumnType("nvarchar(max)") + .HasColumnName("diff"); + + b.Property("OccurredOn") + .HasColumnType("datetimeoffset") + .HasColumnName("occurred_on"); + + b.Property("Resource") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("resource"); + + b.HasKey("Id") + .HasName("pk_audit_events"); + + b.HasIndex("CorrelationId") + .HasDatabaseName("ix_audit_events_correlation_id"); + + b.HasIndex("Actor", "OccurredOn") + .HasDatabaseName("ix_audit_events_actor_occurred_on"); + + b.ToTable("audit_events", null, t => + { + t.HasTrigger("trg_audit_events_no_update_delete"); + }); + + b.HasAnnotation("SqlServer:UseSqlOutputClause", false); + }); + + modelBuilder.Entity("CCE.Domain.Community.Post", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AnsweredReplyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("answered_reply_id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("content"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsAnswerable") + .HasColumnType("bit") + .HasColumnName("is_answerable"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("TopicId") + .HasColumnType("uniqueidentifier") + .HasColumnName("topic_id"); + + b.HasKey("Id") + .HasName("pk_posts"); + + b.HasIndex("TopicId") + .HasDatabaseName("ix_post_topic_id"); + + b.HasIndex("AuthorId", "CreatedOn") + .HasDatabaseName("ix_post_author_created"); + + b.ToTable("posts", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_post_follows"); + + b.HasIndex("PostId", "UserId") + .IsUnique() + .HasDatabaseName("ux_post_follow_post_user"); + + b.ToTable("post_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostRating", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.Property("RatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("rated_on"); + + b.Property("Stars") + .HasColumnType("int") + .HasColumnName("stars"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_post_ratings"); + + b.HasIndex("PostId", "UserId") + .IsUnique() + .HasDatabaseName("ux_post_rating_post_user"); + + b.ToTable("post_ratings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostReply", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("content"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsByExpert") + .HasColumnType("bit") + .HasColumnName("is_by_expert"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("ParentReplyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_reply_id"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.HasKey("Id") + .HasName("pk_post_replies"); + + b.HasIndex("ParentReplyId") + .HasDatabaseName("ix_post_reply_parent_id"); + + b.HasIndex("PostId") + .HasDatabaseName("ix_post_reply_post_id"); + + b.ToTable("post_replies", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.Topic", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_topics"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_topic_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("topics", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.TopicFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("TopicId") + .HasColumnType("uniqueidentifier") + .HasColumnName("topic_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_topic_follows"); + + b.HasIndex("TopicId", "UserId") + .IsUnique() + .HasDatabaseName("ux_topic_follow_topic_user"); + + b.ToTable("topic_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.UserFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedId") + .HasColumnType("uniqueidentifier") + .HasColumnName("followed_id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("FollowerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("follower_id"); + + b.HasKey("Id") + .HasName("pk_user_follows"); + + b.HasIndex("FollowerId", "FollowedId") + .IsUnique() + .HasDatabaseName("ux_user_follow_follower_followed"); + + b.ToTable("user_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.AssetFile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("mime_type"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("original_file_name"); + + b.Property("ScannedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("scanned_on"); + + b.Property("SizeBytes") + .HasColumnType("bigint") + .HasColumnName("size_bytes"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("UploadedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("uploaded_on"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("url"); + + b.Property("VirusScanStatus") + .HasColumnType("int") + .HasColumnName("virus_scan_status"); + + b.HasKey("Id") + .HasName("pk_asset_files"); + + b.HasIndex("VirusScanStatus") + .HasDatabaseName("ix_asset_file_scan_status"); + + b.ToTable("asset_files", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Event", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("EndsOn") + .HasColumnType("datetimeoffset") + .HasColumnName("ends_on"); + + b.Property("FeaturedImageUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("featured_image_url"); + + b.Property("ICalUid") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("i_cal_uid"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LocationAr") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("location_ar"); + + b.Property("LocationEn") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("location_en"); + + b.Property("OnlineMeetingUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("online_meeting_url"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("StartsOn") + .HasColumnType("datetimeoffset") + .HasColumnName("starts_on"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_events"); + + b.HasIndex("ICalUid") + .IsUnique() + .HasDatabaseName("ux_event_ical_uid"); + + b.HasIndex("StartsOn") + .HasDatabaseName("ix_event_starts_on"); + + b.ToTable("events", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.HomepageSection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("SectionType") + .HasColumnType("int") + .HasColumnName("section_type"); + + b.HasKey("Id") + .HasName("pk_homepage_sections"); + + b.HasIndex("IsActive", "OrderIndex") + .HasDatabaseName("ix_homepage_section_active_order"); + + b.ToTable("homepage_sections", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.News", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("FeaturedImageUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("featured_image_url"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsFeatured") + .HasColumnType("bit") + .HasColumnName("is_featured"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PublishedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("published_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("slug"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_news"); + + b.HasIndex("PublishedOn") + .HasDatabaseName("ix_news_published_on"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_news_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("news", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.NewsletterSubscription", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ConfirmationToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("confirmation_token"); + + b.Property("ConfirmedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("confirmed_on"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("nvarchar(320)") + .HasColumnName("email"); + + b.Property("IsConfirmed") + .HasColumnType("bit") + .HasColumnName("is_confirmed"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LocalePreference") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale_preference"); + + b.Property("UnsubscribedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("unsubscribed_on"); + + b.HasKey("Id") + .HasName("pk_newsletter_subscriptions"); + + b.HasIndex("ConfirmationToken") + .HasDatabaseName("ix_newsletter_token"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("ux_newsletter_email"); + + b.ToTable("newsletter_subscriptions", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Page", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PageType") + .HasColumnType("int") + .HasColumnName("page_type"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("slug"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_pages"); + + b.HasIndex("PageType", "Slug") + .IsUnique() + .HasDatabaseName("ux_page_type_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("pages", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Resource", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssetFileId") + .HasColumnType("uniqueidentifier") + .HasColumnName("asset_file_id"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("category_id"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PublishedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("published_on"); + + b.Property("ResourceType") + .HasColumnType("int") + .HasColumnName("resource_type"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("ViewCount") + .HasColumnType("bigint") + .HasColumnName("view_count"); + + b.HasKey("Id") + .HasName("pk_resources"); + + b.HasIndex("AssetFileId") + .HasDatabaseName("ix_resource_asset_file_id"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_resource_country_id"); + + b.HasIndex("CategoryId", "PublishedOn") + .HasDatabaseName("ix_resource_category_published"); + + b.ToTable("resources", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.ResourceCategory", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_resource_categories"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_resource_category_parent_id"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_resource_category_slug"); + + b.ToTable("resource_categories", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.Country", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("FlagUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("flag_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsoAlpha2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("iso_alpha2"); + + b.Property("IsoAlpha3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)") + .HasColumnName("iso_alpha3"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LatestKapsarcSnapshotId") + .HasColumnType("uniqueidentifier") + .HasColumnName("latest_kapsarc_snapshot_id"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("RegionAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("region_ar"); + + b.Property("RegionEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("region_en"); + + b.HasKey("Id") + .HasName("pk_countries"); + + b.HasIndex("IsoAlpha2") + .HasDatabaseName("ix_country_iso_alpha2"); + + b.HasIndex("IsoAlpha3") + .IsUnique() + .HasDatabaseName("ux_country_iso_alpha3_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("countries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryKapsarcSnapshot", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Classification") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("classification"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("PerformanceScore") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)") + .HasColumnName("performance_score"); + + b.Property("SnapshotTakenOn") + .HasColumnType("datetimeoffset") + .HasColumnName("snapshot_taken_on"); + + b.Property("SourceVersion") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)") + .HasColumnName("source_version"); + + b.Property("TotalIndex") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)") + .HasColumnName("total_index"); + + b.HasKey("Id") + .HasName("pk_country_kapsarc_snapshots"); + + b.HasIndex("CountryId", "SnapshotTakenOn") + .HasDatabaseName("ix_kapsarc_snapshot_country_taken"); + + b.ToTable("country_kapsarc_snapshots", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryProfile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContactInfoAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("contact_info_ar"); + + b.Property("ContactInfoEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("contact_info_en"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("KeyInitiativesAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("key_initiatives_ar"); + + b.Property("KeyInitiativesEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("key_initiatives_en"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_country_profiles"); + + b.HasIndex("CountryId") + .IsUnique() + .HasDatabaseName("ux_country_profile_country_id"); + + b.ToTable("country_profiles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryResourceRequest", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AdminNotesAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("admin_notes_ar"); + + b.Property("AdminNotesEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("admin_notes_en"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("ProcessedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("processed_by_id"); + + b.Property("ProcessedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("processed_on"); + + b.Property("ProposedAssetFileId") + .HasColumnType("uniqueidentifier") + .HasColumnName("proposed_asset_file_id"); + + b.Property("ProposedDescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("proposed_description_ar"); + + b.Property("ProposedDescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("proposed_description_en"); + + b.Property("ProposedResourceType") + .HasColumnType("int") + .HasColumnName("proposed_resource_type"); + + b.Property("ProposedTitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("proposed_title_ar"); + + b.Property("ProposedTitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("proposed_title_en"); + + b.Property("RequestedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("requested_by_id"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.HasKey("Id") + .HasName("pk_country_resource_requests"); + + b.HasIndex("CountryId", "Status") + .HasDatabaseName("ix_country_request_country_status"); + + b.ToTable("country_resource_requests", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Evaluation.ServiceEvaluation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentSuitability") + .HasColumnType("int") + .HasColumnName("content_suitability"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("EaseOfUse") + .HasColumnType("int") + .HasColumnName("ease_of_use"); + + b.Property("Feedback") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("feedback"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OverallSatisfaction") + .HasColumnType("int") + .HasColumnName("overall_satisfaction"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_evaluations"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_service_evaluation_created_on"); + + b.ToTable("service_evaluations", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.ExpertProfile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AcademicTitleAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("academic_title_ar"); + + b.Property("AcademicTitleEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("academic_title_en"); + + b.Property("ApprovedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("approved_by_id"); + + b.Property("ApprovedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("approved_on"); + + b.Property("BioAr") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("bio_ar"); + + b.Property("BioEn") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("bio_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.PrimitiveCollection("ExpertiseTags") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("expertise_tags"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_expert_profiles"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("ux_expert_profile_active_user") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("expert_profiles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.ExpertRegistrationRequest", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("ProcessedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("processed_by_id"); + + b.Property("ProcessedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("processed_on"); + + b.Property("RejectionReasonAr") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("rejection_reason_ar"); + + b.Property("RejectionReasonEn") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("rejection_reason_en"); + + b.Property("RequestedBioAr") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("requested_bio_ar"); + + b.Property("RequestedBioEn") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("requested_bio_en"); + + b.Property("RequestedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("requested_by_id"); + + b.PrimitiveCollection("RequestedTags") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("requested_tags"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.HasKey("Id") + .HasName("pk_expert_registration_requests"); + + b.HasIndex("RequestedById") + .HasDatabaseName("ix_expert_request_requested_by"); + + b.HasIndex("Status") + .HasDatabaseName("ix_expert_request_status"); + + b.ToTable("expert_registration_requests", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.RefreshToken", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("created_at_utc"); + + b.Property("CreatedByIp") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("created_by_ip"); + + b.Property("ExpiresAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("expires_at_utc"); + + b.Property("ReplacedByTokenHash") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("replaced_by_token_hash"); + + b.Property("RevokedAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("revoked_at_utc"); + + b.Property("RevokedByIp") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("revoked_by_ip"); + + b.Property("TokenFamilyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("token_family_id"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("token_hash"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("user_agent"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_refresh_tokens"); + + b.HasIndex("TokenFamilyId") + .HasDatabaseName("ix_refresh_tokens_token_family_id"); + + b.HasIndex("TokenHash") + .IsUnique() + .HasDatabaseName("ux_refresh_tokens_token_hash"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_refresh_tokens_user_id"); + + b.ToTable("refresh_tokens", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)") + .HasColumnName("concurrency_stamp"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_name"); + + b.HasKey("Id") + .HasName("pk_asp_net_roles"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[normalized_name] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.StateRepresentativeAssignment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssignedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("assigned_by_id"); + + b.Property("AssignedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("assigned_on"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RevokedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("revoked_by_id"); + + b.Property("RevokedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("revoked_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_state_representative_assignments"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_state_rep_country_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_state_rep_user_id"); + + b.HasIndex("UserId", "CountryId") + .IsUnique() + .HasDatabaseName("ux_state_rep_active_user_country") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("state_representative_assignments", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AccessFailedCount") + .HasColumnType("int") + .HasColumnName("access_failed_count"); + + b.Property("AvatarUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("avatar_url"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)") + .HasColumnName("concurrency_stamp"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("email"); + + b.Property("EmailConfirmed") + .HasColumnType("bit") + .HasColumnName("email_confirmed"); + + b.Property("EntraIdObjectId") + .HasColumnType("uniqueidentifier") + .HasColumnName("entra_id_object_id"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("first_name"); + + b.PrimitiveCollection("Interests") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("interests"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("JobTitle") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("job_title"); + + b.Property("KnowledgeLevel") + .HasColumnType("int") + .HasColumnName("knowledge_level"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("last_name"); + + b.Property("LocalePreference") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale_preference"); + + b.Property("LockoutEnabled") + .HasColumnType("bit") + .HasColumnName("lockout_enabled"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset") + .HasColumnName("lockout_end"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_email"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_user_name"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("organization_name"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)") + .HasColumnName("password_hash"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("phone_number"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasColumnName("phone_number_confirmed"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)") + .HasColumnName("security_stamp"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit") + .HasColumnName("two_factor_enabled"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("pk_asp_net_users"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_users_country_id"); + + b.HasIndex("EntraIdObjectId") + .IsUnique() + .HasDatabaseName("ix_asp_net_users_entra_id_object_id") + .HasFilter("[entra_id_object_id] IS NOT NULL"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[normalized_user_name] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityScenario", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CityType") + .HasColumnType("int") + .HasColumnName("city_type"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("configuration_json"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("TargetYear") + .HasColumnType("int") + .HasColumnName("target_year"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_city_scenarios"); + + b.HasIndex("UserId", "LastModifiedOn") + .HasDatabaseName("ix_city_scenario_user_modified"); + + b.ToTable("city_scenarios", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityScenarioResult", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ComputedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("computed_at"); + + b.Property("ComputedCarbonNeutralityYear") + .HasColumnType("int") + .HasColumnName("computed_carbon_neutrality_year"); + + b.Property("ComputedTotalCostUsd") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("computed_total_cost_usd"); + + b.Property("EngineVersion") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("engine_version"); + + b.Property("ScenarioId") + .HasColumnType("uniqueidentifier") + .HasColumnName("scenario_id"); + + b.HasKey("Id") + .HasName("pk_city_scenario_results"); + + b.HasIndex("ScenarioId", "ComputedAt") + .HasDatabaseName("ix_city_result_scenario_at"); + + b.ToTable("city_scenario_results", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityTechnology", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CarbonImpactKgPerYear") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("carbon_impact_kg_per_year"); + + b.Property("CategoryAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("category_ar"); + + b.Property("CategoryEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("category_en"); + + b.Property("CostUsd") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("cost_usd"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.HasKey("Id") + .HasName("pk_city_technologies"); + + b.HasIndex("IsActive") + .HasDatabaseName("ix_city_tech_is_active"); + + b.ToTable("city_technologies", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMap", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_knowledge_maps"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_knowledge_map_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("knowledge_maps", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapAssociation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssociatedId") + .HasColumnType("uniqueidentifier") + .HasColumnName("associated_id"); + + b.Property("AssociatedType") + .HasColumnType("int") + .HasColumnName("associated_type"); + + b.Property("NodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("node_id"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_associations"); + + b.HasIndex("NodeId", "AssociatedType", "AssociatedId") + .IsUnique() + .HasDatabaseName("ux_km_assoc_node_type_id"); + + b.ToTable("knowledge_map_associations", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapEdge", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FromNodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("from_node_id"); + + b.Property("MapId") + .HasColumnType("uniqueidentifier") + .HasColumnName("map_id"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("RelationshipType") + .HasColumnType("int") + .HasColumnName("relationship_type"); + + b.Property("ToNodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("to_node_id"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_edges"); + + b.HasIndex("FromNodeId") + .HasDatabaseName("ix_km_edge_from_node"); + + b.HasIndex("ToNodeId") + .HasDatabaseName("ix_km_edge_to_node"); + + b.HasIndex("MapId", "FromNodeId", "ToNodeId", "RelationshipType") + .IsUnique() + .HasDatabaseName("ux_km_edge_map_from_to_relation"); + + b.ToTable("knowledge_map_edges", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapNode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("DescriptionAr") + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("LayoutX") + .HasColumnType("float") + .HasColumnName("layout_x"); + + b.Property("LayoutY") + .HasColumnType("float") + .HasColumnName("layout_y"); + + b.Property("MapId") + .HasColumnType("uniqueidentifier") + .HasColumnName("map_id"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("NodeType") + .HasColumnType("int") + .HasColumnName("node_type"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_nodes"); + + b.HasIndex("MapId", "OrderIndex") + .HasDatabaseName("ix_km_node_map_order"); + + b.ToTable("knowledge_map_nodes", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Media.MediaFile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AltTextAr") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("alt_text_ar"); + + b.Property("AltTextEn") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("alt_text_en"); + + b.Property("DescriptionAr") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("mime_type"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("original_file_name"); + + b.Property("SizeBytes") + .HasColumnType("bigint") + .HasColumnName("size_bytes"); + + b.Property("StorageKey") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("storage_key"); + + b.Property("TitleAr") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("title_en"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("UploadedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("uploaded_on"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_media_files"); + + b.ToTable("media_files", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.NotificationLog", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AttemptCount") + .HasColumnType("int") + .HasColumnName("attempt_count"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("correlation_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("Error") + .HasColumnType("nvarchar(max)") + .HasColumnName("error"); + + b.Property("FailedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("failed_on"); + + b.Property("PayloadJson") + .HasColumnType("nvarchar(max)") + .HasColumnName("payload_json"); + + b.Property("ProviderMessageId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("provider_message_id"); + + b.Property("RecipientUserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("recipient_user_id"); + + b.Property("SentOn") + .HasColumnType("datetimeoffset") + .HasColumnName("sent_on"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("template_code"); + + b.Property("TemplateId") + .HasColumnType("uniqueidentifier") + .HasColumnName("template_id"); + + b.HasKey("Id") + .HasName("pk_notification_logs"); + + b.HasIndex("CorrelationId") + .HasDatabaseName("ix_notification_log_correlation_id"); + + b.HasIndex("TemplateCode", "Channel") + .HasDatabaseName("ix_notification_log_template_channel"); + + b.HasIndex("RecipientUserId", "Status", "CreatedOn") + .HasDatabaseName("ix_notification_log_recipient_status_created"); + + b.ToTable("notification_logs", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.NotificationTemplate", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("BodyAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("body_ar"); + + b.Property("BodyEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("body_en"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("code"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("SubjectAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("subject_ar"); + + b.Property("SubjectEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("subject_en"); + + b.Property("VariableSchemaJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("variable_schema_json"); + + b.HasKey("Id") + .HasName("pk_notification_templates"); + + b.HasIndex("Code", "Channel") + .IsUnique() + .HasDatabaseName("ux_notification_template_code_channel"); + + b.ToTable("notification_templates", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.UserNotification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("ReadOn") + .HasColumnType("datetimeoffset") + .HasColumnName("read_on"); + + b.Property("RenderedBody") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("rendered_body"); + + b.Property("RenderedLocale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("rendered_locale"); + + b.Property("RenderedSubjectAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("rendered_subject_ar"); + + b.Property("RenderedSubjectEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("rendered_subject_en"); + + b.Property("SentOn") + .HasColumnType("datetimeoffset") + .HasColumnName("sent_on"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TemplateId") + .HasColumnType("uniqueidentifier") + .HasColumnName("template_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_notifications"); + + b.HasIndex("UserId", "Status") + .HasDatabaseName("ix_user_notification_user_status"); + + b.ToTable("user_notifications", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.UserNotificationSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("EventCode") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("event_code"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasColumnName("is_enabled"); + + b.Property("UpdatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("updated_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_notification_settings"); + + b.HasIndex("UserId", "Channel", "EventCode") + .IsUnique() + .HasDatabaseName("ux_user_notification_settings_user_channel_event") + .HasFilter("[event_code] IS NOT NULL"); + + b.ToTable("user_notification_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("HowToUseVideoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("how_to_use_video_url"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_about_settings"); + + b.ToTable("about_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("about_settings_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_glossary_entries"); + + b.HasIndex("AboutSettingsId") + .HasDatabaseName("ix_glossary_entries_about_settings_id"); + + b.ToTable("glossary_entries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageCountry", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("HomepageSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("homepage_settings_id"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_homepage_countries"); + + b.HasIndex("HomepageSettingsId", "CountryId") + .IsUnique() + .HasDatabaseName("ix_homepage_country_settings_country"); + + b.ToTable("homepage_countries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CceConceptsAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("cce_concepts_ar"); + + b.Property("CceConceptsEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("cce_concepts_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("VideoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("video_url"); + + b.HasKey("Id") + .HasName("pk_homepage_settings"); + + b.ToTable("homepage_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.KnowledgePartner", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("about_settings_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LogoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("logo_url"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("WebsiteUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("website_url"); + + b.HasKey("Id") + .HasName("pk_knowledge_partners"); + + b.HasIndex("AboutSettingsId") + .HasDatabaseName("ix_knowledge_partners_about_settings_id"); + + b.ToTable("knowledge_partners", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PoliciesSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_policies_settings"); + + b.ToTable("policies_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PolicySection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("PoliciesSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("policies_settings_id"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_policy_sections"); + + b.HasIndex("PoliciesSettingsId") + .HasDatabaseName("ix_policy_sections_policies_settings_id"); + + b.ToTable("policy_sections", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Surveys.SearchQueryLog", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("QueryText") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("query_text"); + + b.Property("ResponseTimeMs") + .HasColumnType("int") + .HasColumnName("response_time_ms"); + + b.Property("ResultsCount") + .HasColumnType("int") + .HasColumnName("results_count"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_search_query_logs"); + + b.HasIndex("SubmittedOn") + .HasDatabaseName("ix_search_query_log_submitted_on"); + + b.ToTable("search_query_logs", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Surveys.ServiceRating", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CommentAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("comment_ar"); + + b.Property("CommentEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("comment_en"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("Page") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("page"); + + b.Property("Rating") + .HasColumnType("int") + .HasColumnName("rating"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_ratings"); + + b.HasIndex("SubmittedOn") + .HasDatabaseName("ix_service_rating_submitted_on"); + + b.ToTable("service_ratings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Verification.OtpVerification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AttemptCount") + .HasColumnType("int") + .HasColumnName("attempt_count"); + + b.Property("CodeHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("code_hash"); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("contact"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset") + .HasColumnName("expires_at"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsInvalidated") + .HasColumnType("bit") + .HasColumnName("is_invalidated"); + + b.Property("IsVerified") + .HasColumnType("bit") + .HasColumnName("is_verified"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LastSentAt") + .HasColumnType("datetimeoffset") + .HasColumnName("last_sent_at"); + + b.Property("TypeId") + .HasColumnType("int") + .HasColumnName("type_id"); + + b.HasKey("Id") + .HasName("pk_otp_verifications"); + + b.HasIndex("Contact", "TypeId") + .HasDatabaseName("ix_otp_verifications_contact_type_id"); + + b.ToTable("otp_verifications", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Verification.UserVerification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("contact"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsVerified") + .HasColumnType("bit") + .HasColumnName("is_verified"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("TypeId") + .HasColumnType("int") + .HasColumnName("type_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("VerifiedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("verified_at"); + + b.HasKey("Id") + .HasName("pk_user_verifications"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_user_verifications_user_id"); + + b.HasIndex("Contact", "TypeId") + .IsUnique() + .HasDatabaseName("ix_user_verifications_contact_type_id"); + + b.ToTable("user_verifications", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_type"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_value"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier") + .HasColumnName("role_id"); + + b.HasKey("Id") + .HasName("pk_asp_net_role_claims"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_asp_net_role_claims_role_id"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_type"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_value"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_asp_net_user_claims"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_asp_net_user_claims_user_id"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)") + .HasColumnName("login_provider"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)") + .HasColumnName("provider_key"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)") + .HasColumnName("provider_display_name"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("LoginProvider", "ProviderKey") + .HasName("pk_asp_net_user_logins"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_asp_net_user_logins_user_id"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier") + .HasColumnName("role_id"); + + b.HasKey("UserId", "RoleId") + .HasName("pk_asp_net_user_roles"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_asp_net_user_roles_role_id"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)") + .HasColumnName("login_provider"); + + b.Property("Name") + .HasColumnType("nvarchar(450)") + .HasColumnName("name"); + + b.Property("Value") + .HasColumnType("nvarchar(max)") + .HasColumnName("value"); + + b.HasKey("UserId", "LoginProvider", "Name") + .HasName("pk_asp_net_user_tokens"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.RefreshToken", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_refresh_tokens_asp_net_users_user_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Description", b1 => + { + b1.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b1.HasKey("AboutSettingsId"); + + b1.ToTable("about_settings"); + + b1.WithOwner() + .HasForeignKey("AboutSettingsId") + .HasConstraintName("fk_about_settings_about_settings_id"); + }); + + b.Navigation("Description") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => + { + b.HasOne("CCE.Domain.PlatformSettings.AboutSettings", null) + .WithMany("GlossaryEntries") + .HasForeignKey("AboutSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_glossary_entries_about_settings_about_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Definition", b1 => + { + b1.Property("GlossaryEntryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("definition_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("definition_en"); + + b1.HasKey("GlossaryEntryId"); + + b1.ToTable("glossary_entries"); + + b1.WithOwner() + .HasForeignKey("GlossaryEntryId") + .HasConstraintName("fk_glossary_entries_glossary_entries_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Term", b1 => + { + b1.Property("GlossaryEntryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("term_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("term_en"); + + b1.HasKey("GlossaryEntryId"); + + b1.ToTable("glossary_entries"); + + b1.WithOwner() + .HasForeignKey("GlossaryEntryId") + .HasConstraintName("fk_glossary_entries_glossary_entries_id"); + }); + + b.Navigation("Definition") + .IsRequired(); + + b.Navigation("Term") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageCountry", b => + { + b.HasOne("CCE.Domain.PlatformSettings.HomepageSettings", null) + .WithMany("Countries") + .HasForeignKey("HomepageSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_homepage_countries_homepage_settings_homepage_settings_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Objective", b1 => + { + b1.Property("HomepageSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("objective_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("objective_en"); + + b1.HasKey("HomepageSettingsId"); + + b1.ToTable("homepage_settings"); + + b1.WithOwner() + .HasForeignKey("HomepageSettingsId") + .HasConstraintName("fk_homepage_settings_homepage_settings_id"); + }); + + b.Navigation("Objective") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.KnowledgePartner", b => + { + b.HasOne("CCE.Domain.PlatformSettings.AboutSettings", null) + .WithMany("KnowledgePartners") + .HasForeignKey("AboutSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_knowledge_partners_about_settings_about_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Description", b1 => + { + b1.Property("KnowledgePartnerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b1.HasKey("KnowledgePartnerId"); + + b1.ToTable("knowledge_partners"); + + b1.WithOwner() + .HasForeignKey("KnowledgePartnerId") + .HasConstraintName("fk_knowledge_partners_knowledge_partners_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Name", b1 => + { + b1.Property("KnowledgePartnerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("name_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("name_en"); + + b1.HasKey("KnowledgePartnerId"); + + b1.ToTable("knowledge_partners"); + + b1.WithOwner() + .HasForeignKey("KnowledgePartnerId") + .HasConstraintName("fk_knowledge_partners_knowledge_partners_id"); + }); + + b.Navigation("Description"); + + b.Navigation("Name") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PolicySection", b => + { + b.HasOne("CCE.Domain.PlatformSettings.PoliciesSettings", null) + .WithMany("Sections") + .HasForeignKey("PoliciesSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_policy_sections_policies_settings_policies_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Content", b1 => + { + b1.Property("PolicySectionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b1.Property("En") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b1.HasKey("PolicySectionId"); + + b1.ToTable("policy_sections"); + + b1.WithOwner() + .HasForeignKey("PolicySectionId") + .HasConstraintName("fk_policy_sections_policy_sections_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Title", b1 => + { + b1.Property("PolicySectionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("title_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("title_en"); + + b1.HasKey("PolicySectionId"); + + b1.ToTable("policy_sections"); + + b1.WithOwner() + .HasForeignKey("PolicySectionId") + .HasConstraintName("fk_policy_sections_policy_sections_id"); + }); + + b.Navigation("Content") + .IsRequired(); + + b.Navigation("Title") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.Verification.UserVerification", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_user_verifications_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("CCE.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_role_claims_asp_net_roles_role_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_claims_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_logins_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("CCE.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_roles_asp_net_roles_role_id"); + + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_roles_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_tokens_asp_net_users_user_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.Navigation("GlossaryEntries"); + + b.Navigation("KnowledgePartners"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.Navigation("Countries"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PoliciesSettings", b => + { + b.Navigation("Sections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs new file mode 100644 index 00000000..658203b3 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CCE.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddServiceEvaluation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "service_evaluations", + columns: table => new + { + id = table.Column(type: "uniqueidentifier", nullable: true), + overall_satisfaction = table.Column(type: "int", nullable: false), + ease_of_use = table.Column(type: "int", nullable: false), + content_suitability = table.Column(type: "int", nullable: false), + feedback = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + user_id = table.Column(type: "uniqueidentifier", nullable: true), + created_on = table.Column(type: "datetimeoffset", nullable: false), + created_by_id = table.Column(type: "uniqueidentifier", nullable: true), + last_modified_on = table.Column(type: "datetimeoffset", nullable: true), + last_modified_by_id = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_service_evaluations", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "ix_service_evaluation_created_on", + table: "service_evaluations", + column: "created_on"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "service_evaluations"); + } + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs index 9749ece7..7714ff72 100644 --- a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs @@ -1395,6 +1395,59 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("country_resource_requests", (string)null); }); + modelBuilder.Entity("CCE.Domain.Evaluation.ServiceEvaluation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentSuitability") + .HasColumnType("int") + .HasColumnName("content_suitability"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("EaseOfUse") + .HasColumnType("int") + .HasColumnName("ease_of_use"); + + b.Property("Feedback") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("feedback"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OverallSatisfaction") + .HasColumnType("int") + .HasColumnName("overall_satisfaction"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_evaluations"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_service_evaluation_created_on"); + + b.ToTable("service_evaluations", (string)null); + }); + modelBuilder.Entity("CCE.Domain.Identity.ExpertProfile", b => { b.Property("Id")