From 38ab97ffa5b37633055e6b89e44403ee21403593 Mon Sep 17 00:00:00 2001 From: ahmed Date: Sun, 24 May 2026 15:33:30 +0300 Subject: [PATCH 1/4] feat: add FAQ CRUD administration endpoints and application logic --- .../Endpoints/FaqEndpoints.cs | 96 + backend/src/CCE.Api.Internal/Program.cs | 1 + .../Common/Interfaces/ICceDbContext.cs | 1 + .../Messages/MessageFactory.cs | 1 + .../Commands/CreateFaq/CreateFaqCommand.cs | 11 + .../CreateFaq/CreateFaqCommandHandler.cs | 45 + .../CreateFaq/CreateFaqCommandValidator.cs | 16 + .../Commands/DeleteFaq/DeleteFaqCommand.cs | 6 + .../DeleteFaq/DeleteFaqCommandHandler.cs | 36 + .../DeleteFaq/DeleteFaqCommandValidator.cs | 12 + .../Commands/UpdateFaq/UpdateFaqCommand.cs | 12 + .../UpdateFaq/UpdateFaqCommandHandler.cs | 52 + .../UpdateFaq/UpdateFaqCommandValidator.cs | 17 + .../PlatformSettings/Dtos/FaqDto.cs | 7 + .../Queries/GetFaqById/GetFaqByIdQuery.cs | 7 + .../GetFaqById/GetFaqByIdQueryHandler.cs | 40 + .../Queries/GetFaqs/GetFaqsQuery.cs | 7 + .../Queries/GetFaqs/GetFaqsQueryHandler.cs | 38 + .../src/CCE.Domain/PlatformSettings/Faq.cs | 49 + .../Persistence/CceDbContext.cs | 2 + .../PlatformSettings/FaqConfiguration.cs | 24 + .../20260524103527_CreateFaqTable.Designer.cs | 3798 +++++++++++++++++ .../20260524103527_CreateFaqTable.cs | 42 + .../Migrations/CceDbContextModelSnapshot.cs | 93 + 24 files changed, 4413 insertions(+) create mode 100644 backend/src/CCE.Api.Internal/Endpoints/FaqEndpoints.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommand.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandValidator.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommand.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandValidator.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommand.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandValidator.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Dtos/FaqDto.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQuery.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQueryHandler.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQuery.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQueryHandler.cs create mode 100644 backend/src/CCE.Domain/PlatformSettings/Faq.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Configurations/PlatformSettings/FaqConfiguration.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.Designer.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.cs diff --git a/backend/src/CCE.Api.Internal/Endpoints/FaqEndpoints.cs b/backend/src/CCE.Api.Internal/Endpoints/FaqEndpoints.cs new file mode 100644 index 00000000..b9a9a788 --- /dev/null +++ b/backend/src/CCE.Api.Internal/Endpoints/FaqEndpoints.cs @@ -0,0 +1,96 @@ +using CCE.Api.Common.Extensions; +using CCE.Application.Common; +using CCE.Application.PlatformSettings.Commands.CreateFaq; +using CCE.Application.PlatformSettings.Commands.DeleteFaq; +using CCE.Application.PlatformSettings.Commands.UpdateFaq; +using CCE.Application.PlatformSettings.Queries.GetFaqById; +using CCE.Application.PlatformSettings.Queries.GetFaqs; +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 FaqEndpoints +{ + public static IEndpointRouteBuilder MapFaqEndpoints(this IEndpointRouteBuilder app) + { + var faqs = app.MapGroup("/api/admin/settings/faqs").WithTags("PlatformSettings"); + + faqs.MapGet("", async (IMediator mediator, CancellationToken ct) => + { + var result = await mediator.Send(new GetFaqsQuery(), ct).ConfigureAwait(false); + return result.ToHttpResult(); + }) + .RequireAuthorization(Permissions.Page_PolicyEdit) + .WithName("GetFaqs"); + + faqs.MapGet("/{id:guid}", async ( + System.Guid id, + IMediator mediator, CancellationToken ct) => + { + var result = await mediator.Send(new GetFaqByIdQuery(id), ct).ConfigureAwait(false); + return result.ToHttpResult(); + }) + .RequireAuthorization(Permissions.Page_PolicyEdit) + .WithName("GetFaqById"); + + faqs.MapPost("", async ( + CreateFaqRequest body, + IMediator mediator, CancellationToken ct) => + { + var cmd = new CreateFaqCommand( + body.QuestionAr, body.QuestionEn, + body.AnswerAr, body.AnswerEn, + body.Order); + var result = await mediator.Send(cmd, ct).ConfigureAwait(false); + return result.ToCreatedHttpResult(); + }) + .RequireAuthorization(Permissions.Page_PolicyEdit) + .WithName("CreateFaq"); + + faqs.MapPut("/{id:guid}", async ( + System.Guid id, + UpdateFaqRequest body, + IMediator mediator, CancellationToken ct) => + { + var cmd = new UpdateFaqCommand( + id, + body.QuestionAr, body.QuestionEn, + body.AnswerAr, body.AnswerEn, + body.Order); + var result = await mediator.Send(cmd, ct).ConfigureAwait(false); + return result.ToHttpResult(); + }) + .RequireAuthorization(Permissions.Page_PolicyEdit) + .WithName("UpdateFaq"); + + faqs.MapDelete("/{id:guid}", async ( + System.Guid id, + IMediator mediator, CancellationToken ct) => + { + var result = await mediator.Send(new DeleteFaqCommand(id), ct).ConfigureAwait(false); + return result.ToNoContentHttpResult(); + }) + .RequireAuthorization(Permissions.Page_PolicyEdit) + .WithName("DeleteFaq"); + + return app; + } +} + +public sealed record CreateFaqRequest( + string QuestionAr, + string QuestionEn, + string AnswerAr, + string AnswerEn, + int Order = 0); + +public sealed record UpdateFaqRequest( + string QuestionAr, + string QuestionEn, + string AnswerAr, + string AnswerEn, + int Order); diff --git a/backend/src/CCE.Api.Internal/Program.cs b/backend/src/CCE.Api.Internal/Program.cs index 0f4a848e..a16d98e2 100644 --- a/backend/src/CCE.Api.Internal/Program.cs +++ b/backend/src/CCE.Api.Internal/Program.cs @@ -85,6 +85,7 @@ app.MapHomepageSettingsEndpoints(); app.MapAboutSettingsEndpoints(); app.MapPoliciesSettingsEndpoints(); +app.MapFaqEndpoints(); app.MapMediaEndpoints(); // Sub-11d follow-up — dev sign-in shim. Mounts /dev/sign-in, diff --git a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs index 9e288bd6..df1e52ad 100644 --- a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs +++ b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs @@ -70,6 +70,7 @@ public interface ICceDbContext IQueryable PoliciesSettings { get; } IQueryable KnowledgePartners { get; } IQueryable PolicySections { get; } + IQueryable Faqs { get; } // ─── Verification ─── IQueryable OtpVerifications { get; } diff --git a/backend/src/CCE.Application/Messages/MessageFactory.cs b/backend/src/CCE.Application/Messages/MessageFactory.cs index 5d07dea3..341da348 100644 --- a/backend/src/CCE.Application/Messages/MessageFactory.cs +++ b/backend/src/CCE.Application/Messages/MessageFactory.cs @@ -87,6 +87,7 @@ public FieldError Field(string fieldName, string domainKey) public Response GlossaryEntryNotFound() => NotFound("GLOSSARY_ENTRY_NOT_FOUND"); public Response KnowledgePartnerNotFound() => NotFound("KNOWLEDGE_PARTNER_NOT_FOUND"); public Response PolicySectionNotFound() => NotFound("POLICY_SECTION_NOT_FOUND"); + public Response FaqNotFound() => NotFound("FAQ_NOT_FOUND"); public Response ContentUpdateFailed() => BusinessRule("CONTENT_UPDATE_FAILED"); // ─── Convenience shortcuts (Media domain) ─── diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommand.cs b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommand.cs new file mode 100644 index 00000000..05df6c70 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommand.cs @@ -0,0 +1,11 @@ +using CCE.Application.Common; +using MediatR; + +namespace CCE.Application.PlatformSettings.Commands.CreateFaq; + +public sealed record CreateFaqCommand( + string QuestionAr, + string QuestionEn, + string AnswerAr, + string AnswerEn, + int Order = 0) : IRequest>; diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs new file mode 100644 index 00000000..d9f793b8 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs @@ -0,0 +1,45 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Messages; +using CCE.Domain.Common; +using CCE.Domain.PlatformSettings; +using CCE.Domain.PlatformSettings.ValueObjects; +using MediatR; + +namespace CCE.Application.PlatformSettings.Commands.CreateFaq; + +public sealed class CreateFaqCommandHandler + : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + private readonly ICurrentUserAccessor _currentUser; + private readonly ISystemClock _clock; + + public CreateFaqCommandHandler( + ICceDbContext db, + MessageFactory msg, + ICurrentUserAccessor currentUser, + ISystemClock clock) + { + _db = db; + _msg = msg; + _currentUser = currentUser; + _clock = clock; + } + + public async Task> Handle( + CreateFaqCommand request, CancellationToken cancellationToken) + { + var userId = _currentUser.GetUserId() + ?? throw new DomainException("User identity required."); + var question = LocalizedText.Create(request.QuestionAr, request.QuestionEn); + var answer = LocalizedText.Create(request.AnswerAr, request.AnswerEn); + + var faq = Faq.Create(question, answer, request.Order, userId, _clock); + _db.Add(faq); + await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + + return _msg.Ok(faq.Id, "CONTENT_CREATED"); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandValidator.cs b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandValidator.cs new file mode 100644 index 00000000..bb827150 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; + +namespace CCE.Application.PlatformSettings.Commands.CreateFaq; + +public sealed class CreateFaqCommandValidator + : AbstractValidator +{ + public CreateFaqCommandValidator() + { + RuleFor(x => x.QuestionAr).NotEmpty().MaximumLength(500); + RuleFor(x => x.QuestionEn).NotEmpty().MaximumLength(500); + RuleFor(x => x.AnswerAr).NotEmpty(); + RuleFor(x => x.AnswerEn).NotEmpty(); + RuleFor(x => x.Order).GreaterThanOrEqualTo(0); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommand.cs b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommand.cs new file mode 100644 index 00000000..e04be3f6 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommand.cs @@ -0,0 +1,6 @@ +using CCE.Application.Common; +using MediatR; + +namespace CCE.Application.PlatformSettings.Commands.DeleteFaq; + +public sealed record DeleteFaqCommand(System.Guid Id) : IRequest>; diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs new file mode 100644 index 00000000..2d377cd5 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs @@ -0,0 +1,36 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Messages; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CCE.Application.PlatformSettings.Commands.DeleteFaq; + +public sealed class DeleteFaqCommandHandler + : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public DeleteFaqCommandHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task> Handle( + DeleteFaqCommand request, CancellationToken cancellationToken) + { + var faq = await _db.Faqs + .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) + .ConfigureAwait(false); + + if (faq is null) + return _msg.FaqNotFound(); + + _db.Delete(faq); + await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + + return _msg.Ok("CONTENT_DELETED"); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandValidator.cs b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandValidator.cs new file mode 100644 index 00000000..2d18542c --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace CCE.Application.PlatformSettings.Commands.DeleteFaq; + +public sealed class DeleteFaqCommandValidator + : AbstractValidator +{ + public DeleteFaqCommandValidator() + { + RuleFor(x => x.Id).NotEmpty(); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommand.cs b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommand.cs new file mode 100644 index 00000000..4c078def --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommand.cs @@ -0,0 +1,12 @@ +using CCE.Application.Common; +using MediatR; + +namespace CCE.Application.PlatformSettings.Commands.UpdateFaq; + +public sealed record UpdateFaqCommand( + System.Guid Id, + string QuestionAr, + string QuestionEn, + string AnswerAr, + string AnswerEn, + int Order) : IRequest>; diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs new file mode 100644 index 00000000..1621057f --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs @@ -0,0 +1,52 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Messages; +using CCE.Domain.Common; +using CCE.Domain.PlatformSettings; +using CCE.Domain.PlatformSettings.ValueObjects; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CCE.Application.PlatformSettings.Commands.UpdateFaq; + +public sealed class UpdateFaqCommandHandler + : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + private readonly ICurrentUserAccessor _currentUser; + private readonly ISystemClock _clock; + + public UpdateFaqCommandHandler( + ICceDbContext db, + MessageFactory msg, + ICurrentUserAccessor currentUser, + ISystemClock clock) + { + _db = db; + _msg = msg; + _currentUser = currentUser; + _clock = clock; + } + + public async Task> Handle( + UpdateFaqCommand request, CancellationToken cancellationToken) + { + var faq = await _db.Faqs + .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) + .ConfigureAwait(false); + + if (faq is null) + return _msg.FaqNotFound(); + + var userId = _currentUser.GetUserId() + ?? throw new DomainException("User identity required."); + var question = LocalizedText.Create(request.QuestionAr, request.QuestionEn); + var answer = LocalizedText.Create(request.AnswerAr, request.AnswerEn); + + faq.UpdateContent(question, answer, request.Order, userId, _clock); + await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + + return _msg.Ok(faq.Id, "CONTENT_UPDATED"); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandValidator.cs b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandValidator.cs new file mode 100644 index 00000000..323a9bb2 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandValidator.cs @@ -0,0 +1,17 @@ +using FluentValidation; + +namespace CCE.Application.PlatformSettings.Commands.UpdateFaq; + +public sealed class UpdateFaqCommandValidator + : AbstractValidator +{ + public UpdateFaqCommandValidator() + { + RuleFor(x => x.Id).NotEmpty(); + RuleFor(x => x.QuestionAr).NotEmpty().MaximumLength(500); + RuleFor(x => x.QuestionEn).NotEmpty().MaximumLength(500); + RuleFor(x => x.AnswerAr).NotEmpty(); + RuleFor(x => x.AnswerEn).NotEmpty(); + RuleFor(x => x.Order).GreaterThanOrEqualTo(0); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Dtos/FaqDto.cs b/backend/src/CCE.Application/PlatformSettings/Dtos/FaqDto.cs new file mode 100644 index 00000000..3a380c11 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Dtos/FaqDto.cs @@ -0,0 +1,7 @@ +namespace CCE.Application.PlatformSettings.Dtos; + +public sealed record FaqDto( + System.Guid Id, + LocalizedTextDto Question, + LocalizedTextDto Answer, + int Order); diff --git a/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQuery.cs b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQuery.cs new file mode 100644 index 00000000..d25210c4 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQuery.cs @@ -0,0 +1,7 @@ +using CCE.Application.Common; +using CCE.Application.PlatformSettings.Dtos; +using MediatR; + +namespace CCE.Application.PlatformSettings.Queries.GetFaqById; + +public sealed record GetFaqByIdQuery(System.Guid Id) : IRequest>; diff --git a/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQueryHandler.cs b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQueryHandler.cs new file mode 100644 index 00000000..54b93cbe --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqById/GetFaqByIdQueryHandler.cs @@ -0,0 +1,40 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Messages; +using CCE.Application.PlatformSettings.Dtos; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CCE.Application.PlatformSettings.Queries.GetFaqById; + +public sealed class GetFaqByIdQueryHandler + : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public GetFaqByIdQueryHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task> Handle( + GetFaqByIdQuery request, CancellationToken cancellationToken) + { + var faq = await _db.Faqs + .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) + .ConfigureAwait(false); + + if (faq is null) + return _msg.FaqNotFound(); + + var dto = new FaqDto( + faq.Id, + new LocalizedTextDto(faq.Question.Ar, faq.Question.En), + new LocalizedTextDto(faq.Answer.Ar, faq.Answer.En), + faq.Order); + + return _msg.Ok(dto, "ITEMS_LISTED"); + } +} diff --git a/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQuery.cs b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQuery.cs new file mode 100644 index 00000000..c68c0760 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQuery.cs @@ -0,0 +1,7 @@ +using CCE.Application.Common; +using CCE.Application.PlatformSettings.Dtos; +using MediatR; + +namespace CCE.Application.PlatformSettings.Queries.GetFaqs; + +public sealed record GetFaqsQuery : IRequest>>; diff --git a/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQueryHandler.cs b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQueryHandler.cs new file mode 100644 index 00000000..a3d5c9e0 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Queries/GetFaqs/GetFaqsQueryHandler.cs @@ -0,0 +1,38 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Common.Pagination; +using CCE.Application.Messages; +using CCE.Application.PlatformSettings.Dtos; +using MediatR; + +namespace CCE.Application.PlatformSettings.Queries.GetFaqs; + +public sealed class GetFaqsQueryHandler + : IRequestHandler>> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public GetFaqsQueryHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task>> Handle( + GetFaqsQuery request, CancellationToken cancellationToken) + { + var faqs = await _db.Faqs + .OrderBy(f => f.Order) + .ToListAsyncEither(cancellationToken) + .ConfigureAwait(false); + + var dtos = faqs.Select(f => new FaqDto( + f.Id, + new LocalizedTextDto(f.Question.Ar, f.Question.En), + new LocalizedTextDto(f.Answer.Ar, f.Answer.En), + f.Order)).ToList(); + + return _msg.Ok>(dtos, "ITEMS_LISTED"); + } +} diff --git a/backend/src/CCE.Domain/PlatformSettings/Faq.cs b/backend/src/CCE.Domain/PlatformSettings/Faq.cs new file mode 100644 index 00000000..daa7e21a --- /dev/null +++ b/backend/src/CCE.Domain/PlatformSettings/Faq.cs @@ -0,0 +1,49 @@ +using CCE.Domain.Common; +using CCE.Domain.PlatformSettings.ValueObjects; + +namespace CCE.Domain.PlatformSettings; + +public sealed class Faq : AuditableEntity +{ + private Faq() : base(System.Guid.Empty) { } + + private Faq( + System.Guid id, + LocalizedText question, + LocalizedText answer, + int order) : base(id) + { + Question = question; + Answer = answer; + Order = order; + } + + public LocalizedText Question { get; private set; } = null!; + public LocalizedText Answer { get; private set; } = null!; + public int Order { get; private set; } + + public static Faq Create( + LocalizedText question, + LocalizedText answer, + int order, + System.Guid by, + ISystemClock clock) + { + var faq = new Faq(System.Guid.NewGuid(), question, answer, order); + faq.MarkAsCreated(by, clock); + return faq; + } + + public void UpdateContent( + LocalizedText question, + LocalizedText answer, + int order, + System.Guid by, + ISystemClock clock) + { + Question = question; + Answer = answer; + Order = order; + MarkAsModified(by, clock); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs index cff5f50f..1833ebfa 100644 --- a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs +++ b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs @@ -101,6 +101,7 @@ public CceDbContext(DbContextOptions options) : base(options) { } public DbSet PoliciesSettings => Set(); public DbSet KnowledgePartners => Set(); public DbSet PolicySections => Set(); + public DbSet Faqs => Set(); // ─── ICceDbContext (read-only queryables — no tracking) ─── IQueryable ICceDbContext.Users => Users.AsNoTracking(); @@ -148,6 +149,7 @@ public CceDbContext(DbContextOptions options) : base(options) { } IQueryable ICceDbContext.PoliciesSettings => PoliciesSettings.AsNoTracking(); IQueryable ICceDbContext.KnowledgePartners => KnowledgePartners.AsNoTracking(); IQueryable ICceDbContext.PolicySections => PolicySections.AsNoTracking(); + IQueryable ICceDbContext.Faqs => Faqs.AsNoTracking(); IQueryable ICceDbContext.OtpVerifications => OtpVerifications.AsNoTracking(); IQueryable ICceDbContext.UserVerifications => UserVerifications.AsNoTracking(); IQueryable ICceDbContext.MediaFiles => MediaFiles.AsNoTracking(); diff --git a/backend/src/CCE.Infrastructure/Persistence/Configurations/PlatformSettings/FaqConfiguration.cs b/backend/src/CCE.Infrastructure/Persistence/Configurations/PlatformSettings/FaqConfiguration.cs new file mode 100644 index 00000000..cc9b18b7 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Configurations/PlatformSettings/FaqConfiguration.cs @@ -0,0 +1,24 @@ +using CCE.Domain.PlatformSettings; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CCE.Infrastructure.Persistence.Configurations.PlatformSettings; + +internal sealed class FaqConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(f => f.Id); + builder.Property(f => f.Id).ValueGeneratedNever(); + builder.OwnsOne(f => f.Question, question => + { + question.Property(q => q.Ar).HasMaxLength(500).IsRequired(); + question.Property(q => q.En).HasMaxLength(500).IsRequired(); + }); + builder.OwnsOne(f => f.Answer, answer => + { + answer.Property(a => a.Ar).HasColumnType("nvarchar(max)").IsRequired(); + answer.Property(a => a.En).HasColumnType("nvarchar(max)").IsRequired(); + }); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.Designer.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.Designer.cs new file mode 100644 index 00000000..b456df08 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.Designer.cs @@ -0,0 +1,3798 @@ +// +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("20260524103527_CreateFaqTable")] + partial class CreateFaqTable + { + /// + 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.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.Faq", 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("Order") + .HasColumnType("int") + .HasColumnName("order"); + + b.HasKey("Id") + .HasName("pk_faqs"); + + b.ToTable("faqs", (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.Faq", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Answer", b1 => + { + b1.Property("FaqId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("answer_ar"); + + b1.Property("En") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("answer_en"); + + b1.HasKey("FaqId"); + + b1.ToTable("faqs"); + + b1.WithOwner() + .HasForeignKey("FaqId") + .HasConstraintName("fk_faqs_faqs_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Question", b1 => + { + b1.Property("FaqId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("question_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("question_en"); + + b1.HasKey("FaqId"); + + b1.ToTable("faqs"); + + b1.WithOwner() + .HasForeignKey("FaqId") + .HasConstraintName("fk_faqs_faqs_id"); + }); + + b.Navigation("Answer") + .IsRequired(); + + b.Navigation("Question") + .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/20260524103527_CreateFaqTable.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.cs new file mode 100644 index 00000000..f069d2f5 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260524103527_CreateFaqTable.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CCE.Infrastructure.Persistence.Migrations +{ + /// + public partial class CreateFaqTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "faqs", + columns: table => new + { + id = table.Column(type: "uniqueidentifier", nullable: false), + question_ar = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + question_en = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + answer_ar = table.Column(type: "nvarchar(max)", nullable: false), + answer_en = table.Column(type: "nvarchar(max)", nullable: false), + order = table.Column(type: "int", nullable: false), + created_on = table.Column(type: "datetimeoffset", nullable: false), + created_by_id = table.Column(type: "uniqueidentifier", nullable: false), + last_modified_on = table.Column(type: "datetimeoffset", nullable: true), + last_modified_by_id = table.Column(type: "uniqueidentifier", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_faqs", x => x.id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "faqs"); + } + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs index eebf84ba..796f15f4 100644 --- a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs @@ -2667,6 +2667,38 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("about_settings", (string)null); }); + modelBuilder.Entity("CCE.Domain.PlatformSettings.Faq", 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("Order") + .HasColumnType("int") + .HasColumnName("order"); + + b.HasKey("Id") + .HasName("pk_faqs"); + + b.ToTable("faqs", (string)null); + }); + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => { b.Property("Id") @@ -3365,6 +3397,67 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired(); }); + modelBuilder.Entity("CCE.Domain.PlatformSettings.Faq", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Answer", b1 => + { + b1.Property("FaqId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("answer_ar"); + + b1.Property("En") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("answer_en"); + + b1.HasKey("FaqId"); + + b1.ToTable("faqs"); + + b1.WithOwner() + .HasForeignKey("FaqId") + .HasConstraintName("fk_faqs_faqs_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Question", b1 => + { + b1.Property("FaqId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("question_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("question_en"); + + b1.HasKey("FaqId"); + + b1.ToTable("faqs"); + + b1.WithOwner() + .HasForeignKey("FaqId") + .HasConstraintName("fk_faqs_faqs_id"); + }); + + b.Navigation("Answer") + .IsRequired(); + + b.Navigation("Question") + .IsRequired(); + }); + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => { b.HasOne("CCE.Domain.PlatformSettings.AboutSettings", null) From a4a22c8a94c115afae469528da0cf162baaa3220 Mon Sep 17 00:00:00 2001 From: ahmed Date: Sun, 24 May 2026 15:52:46 +0300 Subject: [PATCH 2/4] feat: add public FAQ endpoints and queries for external users --- .../Endpoints/FaqPublicEndpoints.cs | 26 +++++++++++++ backend/src/CCE.Api.External/Program.cs | 1 + .../Public/Dtos/PublicFaqDto.cs | 8 ++++ .../GetPublicFaqs/GetPublicFaqsQuery.cs | 7 ++++ .../GetPublicFaqsQueryHandler.cs | 38 +++++++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 backend/src/CCE.Api.External/Endpoints/FaqPublicEndpoints.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Public/Dtos/PublicFaqDto.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQuery.cs create mode 100644 backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQueryHandler.cs diff --git a/backend/src/CCE.Api.External/Endpoints/FaqPublicEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/FaqPublicEndpoints.cs new file mode 100644 index 00000000..9a656f3b --- /dev/null +++ b/backend/src/CCE.Api.External/Endpoints/FaqPublicEndpoints.cs @@ -0,0 +1,26 @@ +using CCE.Api.Common.Extensions; +using CCE.Application.PlatformSettings.Public.Queries.GetPublicFaqs; +using MediatR; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace CCE.Api.External.Endpoints; + +public static class FaqPublicEndpoints +{ + public static IEndpointRouteBuilder MapFaqPublicEndpoints(this IEndpointRouteBuilder app) + { + var faqs = app.MapGroup("/api/faqs").WithTags("FAQ"); + + faqs.MapGet("", async (IMediator mediator, CancellationToken ct) => + { + var result = await mediator.Send(new GetPublicFaqsQuery(), ct).ConfigureAwait(false); + return result.ToHttpResult(); + }) + .AllowAnonymous() + .WithName("GetPublicFaqs"); + + return app; + } +} diff --git a/backend/src/CCE.Api.External/Program.cs b/backend/src/CCE.Api.External/Program.cs index 175570ef..8a720461 100644 --- a/backend/src/CCE.Api.External/Program.cs +++ b/backend/src/CCE.Api.External/Program.cs @@ -116,6 +116,7 @@ app.MapHomepageSettingsPublicEndpoints(); app.MapAboutSettingsPublicEndpoints(); app.MapPoliciesSettingsPublicEndpoints(); +app.MapFaqPublicEndpoints(); app.MapMediaPublicEndpoints(); app.MapVerificationEndpoints(); diff --git a/backend/src/CCE.Application/PlatformSettings/Public/Dtos/PublicFaqDto.cs b/backend/src/CCE.Application/PlatformSettings/Public/Dtos/PublicFaqDto.cs new file mode 100644 index 00000000..279fd396 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Public/Dtos/PublicFaqDto.cs @@ -0,0 +1,8 @@ +using CCE.Application.PlatformSettings.Dtos; + +namespace CCE.Application.PlatformSettings.Public.Dtos; + +public sealed record PublicFaqDto( + LocalizedTextDto Question, + LocalizedTextDto Answer, + int Order); diff --git a/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQuery.cs b/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQuery.cs new file mode 100644 index 00000000..76ee7d77 --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQuery.cs @@ -0,0 +1,7 @@ +using CCE.Application.Common; +using CCE.Application.PlatformSettings.Public.Dtos; +using MediatR; + +namespace CCE.Application.PlatformSettings.Public.Queries.GetPublicFaqs; + +public sealed record GetPublicFaqsQuery : IRequest>>; diff --git a/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQueryHandler.cs b/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQueryHandler.cs new file mode 100644 index 00000000..f1436d0a --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/Public/Queries/GetPublicFaqs/GetPublicFaqsQueryHandler.cs @@ -0,0 +1,38 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Common.Pagination; +using CCE.Application.Messages; +using CCE.Application.PlatformSettings.Dtos; +using CCE.Application.PlatformSettings.Public.Dtos; +using MediatR; + +namespace CCE.Application.PlatformSettings.Public.Queries.GetPublicFaqs; + +public sealed class GetPublicFaqsQueryHandler + : IRequestHandler>> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; + + public GetPublicFaqsQueryHandler(ICceDbContext db, MessageFactory msg) + { + _db = db; + _msg = msg; + } + + public async Task>> Handle( + GetPublicFaqsQuery request, CancellationToken cancellationToken) + { + var faqs = await _db.Faqs + .OrderBy(f => f.Order) + .ToListAsyncEither(cancellationToken) + .ConfigureAwait(false); + + var dtos = faqs.Select(f => new PublicFaqDto( + new LocalizedTextDto(f.Question.Ar, f.Question.En), + new LocalizedTextDto(f.Answer.Ar, f.Answer.En), + f.Order)).ToList(); + + return _msg.Ok>(dtos, "ITEMS_LISTED"); + } +} From 2946744bc1985d3ecb38c02b4fdacd7d65195dfc Mon Sep 17 00:00:00 2001 From: ahmed Date: Mon, 25 May 2026 09:41:39 +0300 Subject: [PATCH 3/4] refactor: introduce IFaqRepository and update FAQ command handlers to use it --- .../CreateFaq/CreateFaqCommandHandler.cs | 5 ++++- .../DeleteFaq/DeleteFaqCommandHandler.cs | 10 ++++----- .../UpdateFaq/UpdateFaqCommandHandler.cs | 7 +++--- .../PlatformSettings/IFaqRepository.cs | 11 ++++++++++ .../PlatformSettings/FaqRepository.cs | 22 +++++++++++++++++++ 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 backend/src/CCE.Application/PlatformSettings/IFaqRepository.cs create mode 100644 backend/src/CCE.Infrastructure/PlatformSettings/FaqRepository.cs diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs index d9f793b8..8c690e77 100644 --- a/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs +++ b/backend/src/CCE.Application/PlatformSettings/Commands/CreateFaq/CreateFaqCommandHandler.cs @@ -11,17 +11,20 @@ namespace CCE.Application.PlatformSettings.Commands.CreateFaq; public sealed class CreateFaqCommandHandler : IRequestHandler> { + private readonly IFaqRepository _repo; private readonly ICceDbContext _db; private readonly MessageFactory _msg; private readonly ICurrentUserAccessor _currentUser; private readonly ISystemClock _clock; public CreateFaqCommandHandler( + IFaqRepository repo, ICceDbContext db, MessageFactory msg, ICurrentUserAccessor currentUser, ISystemClock clock) { + _repo = repo; _db = db; _msg = msg; _currentUser = currentUser; @@ -37,7 +40,7 @@ public CreateFaqCommandHandler( var answer = LocalizedText.Create(request.AnswerAr, request.AnswerEn); var faq = Faq.Create(question, answer, request.Order, userId, _clock); - _db.Add(faq); + _repo.Add(faq); await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return _msg.Ok(faq.Id, "CONTENT_CREATED"); diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs index 2d377cd5..5d18fa75 100644 --- a/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs +++ b/backend/src/CCE.Application/PlatformSettings/Commands/DeleteFaq/DeleteFaqCommandHandler.cs @@ -2,18 +2,19 @@ using CCE.Application.Common.Interfaces; using CCE.Application.Messages; using MediatR; -using Microsoft.EntityFrameworkCore; namespace CCE.Application.PlatformSettings.Commands.DeleteFaq; public sealed class DeleteFaqCommandHandler : IRequestHandler> { + private readonly IFaqRepository _repo; private readonly ICceDbContext _db; private readonly MessageFactory _msg; - public DeleteFaqCommandHandler(ICceDbContext db, MessageFactory msg) + public DeleteFaqCommandHandler(IFaqRepository repo, ICceDbContext db, MessageFactory msg) { + _repo = repo; _db = db; _msg = msg; } @@ -21,14 +22,13 @@ public DeleteFaqCommandHandler(ICceDbContext db, MessageFactory msg) public async Task> Handle( DeleteFaqCommand request, CancellationToken cancellationToken) { - var faq = await _db.Faqs - .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) + var faq = await _repo.GetByIdAsync(request.Id, cancellationToken) .ConfigureAwait(false); if (faq is null) return _msg.FaqNotFound(); - _db.Delete(faq); + _repo.Delete(faq); await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); return _msg.Ok("CONTENT_DELETED"); diff --git a/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs index 1621057f..55f01a1e 100644 --- a/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs +++ b/backend/src/CCE.Application/PlatformSettings/Commands/UpdateFaq/UpdateFaqCommandHandler.cs @@ -5,24 +5,26 @@ using CCE.Domain.PlatformSettings; using CCE.Domain.PlatformSettings.ValueObjects; using MediatR; -using Microsoft.EntityFrameworkCore; namespace CCE.Application.PlatformSettings.Commands.UpdateFaq; public sealed class UpdateFaqCommandHandler : IRequestHandler> { + private readonly IFaqRepository _repo; private readonly ICceDbContext _db; private readonly MessageFactory _msg; private readonly ICurrentUserAccessor _currentUser; private readonly ISystemClock _clock; public UpdateFaqCommandHandler( + IFaqRepository repo, ICceDbContext db, MessageFactory msg, ICurrentUserAccessor currentUser, ISystemClock clock) { + _repo = repo; _db = db; _msg = msg; _currentUser = currentUser; @@ -32,8 +34,7 @@ public UpdateFaqCommandHandler( public async Task> Handle( UpdateFaqCommand request, CancellationToken cancellationToken) { - var faq = await _db.Faqs - .FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken) + var faq = await _repo.GetByIdAsync(request.Id, cancellationToken) .ConfigureAwait(false); if (faq is null) diff --git a/backend/src/CCE.Application/PlatformSettings/IFaqRepository.cs b/backend/src/CCE.Application/PlatformSettings/IFaqRepository.cs new file mode 100644 index 00000000..6fa109ec --- /dev/null +++ b/backend/src/CCE.Application/PlatformSettings/IFaqRepository.cs @@ -0,0 +1,11 @@ +using CCE.Domain.PlatformSettings; + +namespace CCE.Application.PlatformSettings; + +/// Repository for the standalone FAQ entity. +public interface IFaqRepository +{ + Task GetByIdAsync(System.Guid id, CancellationToken ct); + void Add(Faq faq); + void Delete(Faq faq); +} diff --git a/backend/src/CCE.Infrastructure/PlatformSettings/FaqRepository.cs b/backend/src/CCE.Infrastructure/PlatformSettings/FaqRepository.cs new file mode 100644 index 00000000..737c046c --- /dev/null +++ b/backend/src/CCE.Infrastructure/PlatformSettings/FaqRepository.cs @@ -0,0 +1,22 @@ +using CCE.Application.PlatformSettings; +using CCE.Domain.PlatformSettings; +using CCE.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace CCE.Infrastructure.PlatformSettings; + +public sealed class FaqRepository : IFaqRepository +{ + private readonly CceDbContext _db; + + public FaqRepository(CceDbContext db) => _db = db; + + public async Task GetByIdAsync(System.Guid id, CancellationToken ct) + => await _db.Faqs + .FirstOrDefaultAsync(f => f.Id == id, ct) + .ConfigureAwait(false); + + public void Add(Faq faq) => _db.Faqs.Add(faq); + + public void Delete(Faq faq) => _db.Faqs.Remove(faq); +} From 9bf03c3e62f12bcd5d3329fa499f6a424bc108c9 Mon Sep 17 00:00:00 2001 From: ahmed Date: Mon, 25 May 2026 16:26:37 +0300 Subject: [PATCH 4/4] update Dependency injection --- backend/src/CCE.Infrastructure/DependencyInjection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/CCE.Infrastructure/DependencyInjection.cs b/backend/src/CCE.Infrastructure/DependencyInjection.cs index fb63c3bc..5c66d3ac 100644 --- a/backend/src/CCE.Infrastructure/DependencyInjection.cs +++ b/backend/src/CCE.Infrastructure/DependencyInjection.cs @@ -187,6 +187,7 @@ public static IServiceCollection AddInfrastructure( services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped();