Skip to content

Commit ff201c9

Browse files
committed
feat(platform-settings): restructure CMS settings with child entity tables
- Replace bilingual video_url with single field on HomepageSettings - Extract KnowledgePartner as separate aggregate from AboutSettings - Extract PolicySection with PolicySectionType enum from PoliciesSettings - Promote GlossaryEntry, HomepageCountry to AggregateRoot<Guid> - Add order_index to HomepageCountry and all collection tables - Switch all handlers to Response<T> + MessageFactory pattern - Add ICceDbContext.Add/Delete/DeleteRange generic write methods - Add 12 new admin + public endpoints for CRUD (glossary, partners, sections) - Register new repos, EF configs, DI, SystemCode mappings, Resources.yaml keys - Regenerate AddPlatformSettings migration with updated schema (7 tables)
1 parent 0abed39 commit ff201c9

105 files changed

Lines changed: 6259 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/permissions.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ groups:
9595
Edit:
9696
description: Edit static pages (about, terms, privacy)
9797
roles: [cce-super-admin, cce-admin, cce-content-manager]
98+
PolicyEdit:
99+
description: Edit policies & terms settings (restricted)
100+
roles: [cce-super-admin]
98101
Country:
99102
Profile:
100103
Update:

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,37 @@ SCENARIO_NOT_FOUND:
347347
TECHNOLOGY_NOT_FOUND:
348348
ar: "التقنية غير موجودة"
349349
en: "Technology not found"
350+
351+
# ─── Platform Settings ───
352+
353+
HOMEPAGE_SETTINGS_NOT_FOUND:
354+
ar: "لم يتم العثور على إعدادات الصفحة الرئيسية"
355+
en: "Homepage settings not found"
356+
357+
ABOUT_SETTINGS_NOT_FOUND:
358+
ar: "لم يتم العثور على إعدادات عن المنصة"
359+
en: "About settings not found"
360+
361+
POLICIES_SETTINGS_NOT_FOUND:
362+
ar: "لم يتم العثور على إعدادات السياسات"
363+
en: "Policies settings not found"
364+
365+
GLOSSARY_ENTRY_NOT_FOUND:
366+
ar: "لم يتم العثور على المصطلح"
367+
en: "Glossary entry not found"
368+
369+
KNOWLEDGE_PARTNER_NOT_FOUND:
370+
ar: "لم يتم العثور على شريك المعرفة"
371+
en: "Knowledge partner not found"
372+
373+
POLICY_SECTION_NOT_FOUND:
374+
ar: "لم يتم العثور على القسم"
375+
en: "Policy section not found"
376+
377+
SETTINGS_UPDATED:
378+
ar: "تمت عملية التحديث بنجاح"
379+
en: "Content update success"
380+
381+
CONTENT_UPDATE_FAILED:
382+
ar: "عذراً، حدثت مشكلة أثناء تحديث المحتوى"
383+
en: "Sorry, a problem occurred while updating the content"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.PlatformSettings.Public.Queries.GetPublicAboutSettings;
3+
using MediatR;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace CCE.Api.External.Endpoints;
9+
10+
public static class AboutSettingsPublicEndpoints
11+
{
12+
public static IEndpointRouteBuilder MapAboutSettingsPublicEndpoints(this IEndpointRouteBuilder app)
13+
{
14+
var about = app.MapGroup("/api/about").WithTags("About");
15+
16+
about.MapGet("", async (IMediator mediator, CancellationToken ct) =>
17+
{
18+
var result = await mediator.Send(new GetPublicAboutSettingsQuery(), ct).ConfigureAwait(false);
19+
return result.ToHttpResult();
20+
})
21+
.AllowAnonymous()
22+
.WithName("GetPublicAboutSettings");
23+
24+
return app;
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.PlatformSettings.Public.Queries.GetPublicHomepage;
3+
using MediatR;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace CCE.Api.External.Endpoints;
9+
10+
public static class HomepageSettingsPublicEndpoints
11+
{
12+
public static IEndpointRouteBuilder MapHomepageSettingsPublicEndpoints(this IEndpointRouteBuilder app)
13+
{
14+
var homepage = app.MapGroup("/api/homepage").WithTags("Homepage");
15+
16+
homepage.MapGet("", async (IMediator mediator, CancellationToken ct) =>
17+
{
18+
var result = await mediator.Send(new GetPublicHomepageQuery(), ct).ConfigureAwait(false);
19+
return result.ToHttpResult();
20+
})
21+
.AllowAnonymous()
22+
.WithName("GetPublicHomepage");
23+
24+
return app;
25+
}
26+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.PlatformSettings.Public.Queries.GetPublicPoliciesSettings;
3+
using MediatR;
4+
using Microsoft.AspNetCore.Builder;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace CCE.Api.External.Endpoints;
9+
10+
public static class PoliciesSettingsPublicEndpoints
11+
{
12+
public static IEndpointRouteBuilder MapPoliciesSettingsPublicEndpoints(this IEndpointRouteBuilder app)
13+
{
14+
var policies = app.MapGroup("/api/policies").WithTags("Policies");
15+
16+
policies.MapGet("", async (IMediator mediator, CancellationToken ct) =>
17+
{
18+
var result = await mediator.Send(new GetPublicPoliciesSettingsQuery(), ct).ConfigureAwait(false);
19+
return result.ToHttpResult();
20+
})
21+
.AllowAnonymous()
22+
.WithName("GetPublicPoliciesSettings");
23+
24+
return app;
25+
}
26+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
app.MapAssistantEndpoints();
104104
app.MapKapsarcEndpoints();
105105
app.MapSurveysEndpoints();
106+
app.MapHomepageSettingsPublicEndpoints();
107+
app.MapAboutSettingsPublicEndpoints();
108+
app.MapPoliciesSettingsPublicEndpoints();
106109

107110
app.MapGet("/health", async (IMediator mediator) =>
108111
{
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.Common;
3+
using CCE.Application.PlatformSettings.Commands.CreateGlossaryEntry;
4+
using CCE.Application.PlatformSettings.Commands.CreateKnowledgePartner;
5+
using CCE.Application.PlatformSettings.Commands.DeleteGlossaryEntry;
6+
using CCE.Application.PlatformSettings.Commands.DeleteKnowledgePartner;
7+
using CCE.Application.PlatformSettings.Commands.UpdateAboutSettings;
8+
using CCE.Application.PlatformSettings.Commands.UpdateGlossaryEntry;
9+
using CCE.Application.PlatformSettings.Commands.UpdateKnowledgePartner;
10+
using CCE.Application.PlatformSettings.Queries.GetAboutSettings;
11+
using CCE.Domain;
12+
using MediatR;
13+
using Microsoft.AspNetCore.Builder;
14+
using Microsoft.AspNetCore.Http;
15+
using Microsoft.AspNetCore.Routing;
16+
17+
namespace CCE.Api.Internal.Endpoints;
18+
19+
public static class AboutSettingsEndpoints
20+
{
21+
public static IEndpointRouteBuilder MapAboutSettingsEndpoints(this IEndpointRouteBuilder app)
22+
{
23+
var about = app.MapGroup("/api/admin/settings/about").WithTags("PlatformSettings");
24+
25+
about.MapGet("", async (IMediator mediator, CancellationToken ct) =>
26+
{
27+
var result = await mediator.Send(new GetAboutSettingsQuery(), ct).ConfigureAwait(false);
28+
return result.ToHttpResult();
29+
})
30+
.RequireAuthorization(Permissions.Page_Edit)
31+
.WithName("GetAboutSettings");
32+
33+
about.MapPut("", async (UpdateAboutSettingsRequest body, IMediator mediator, CancellationToken ct) =>
34+
{
35+
var rowVersion = string.IsNullOrEmpty(body.RowVersion)
36+
? System.Array.Empty<byte>()
37+
: System.Convert.FromBase64String(body.RowVersion);
38+
var cmd = new UpdateAboutSettingsCommand(
39+
body.DescriptionAr, body.DescriptionEn,
40+
body.HowToUseVideoUrl, rowVersion);
41+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
42+
return result.ToHttpResult();
43+
})
44+
.RequireAuthorization(Permissions.Page_Edit)
45+
.WithName("UpdateAboutSettings");
46+
47+
about.MapPost("/glossary", async (CreateGlossaryEntryRequest body, IMediator mediator, CancellationToken ct) =>
48+
{
49+
var cmd = new CreateGlossaryEntryCommand(
50+
body.TermAr, body.TermEn, body.DefinitionAr, body.DefinitionEn);
51+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
52+
return result.ToCreatedHttpResult();
53+
})
54+
.RequireAuthorization(Permissions.Page_Edit)
55+
.WithName("CreateGlossaryEntry");
56+
57+
about.MapPut("/glossary/{id:guid}", async (
58+
System.Guid id,
59+
UpdateGlossaryEntryRequest body,
60+
IMediator mediator, CancellationToken ct) =>
61+
{
62+
var cmd = new UpdateGlossaryEntryCommand(
63+
id, body.TermAr, body.TermEn, body.DefinitionAr, body.DefinitionEn);
64+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
65+
return result.ToHttpResult();
66+
})
67+
.RequireAuthorization(Permissions.Page_Edit)
68+
.WithName("UpdateGlossaryEntry");
69+
70+
about.MapDelete("/glossary/{id:guid}", async (
71+
System.Guid id,
72+
IMediator mediator, CancellationToken ct) =>
73+
{
74+
var result = await mediator.Send(new DeleteGlossaryEntryCommand(id), ct).ConfigureAwait(false);
75+
return result.ToNoContentHttpResult();
76+
})
77+
.RequireAuthorization(Permissions.Page_Edit)
78+
.WithName("DeleteGlossaryEntry");
79+
80+
about.MapPost("/knowledge-partners", async (
81+
CreateKnowledgePartnerRequest body,
82+
IMediator mediator, CancellationToken ct) =>
83+
{
84+
var cmd = new CreateKnowledgePartnerCommand(
85+
body.NameAr, body.NameEn, body.LogoUrl, body.WebsiteUrl,
86+
body.DescriptionAr, body.DescriptionEn);
87+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
88+
return result.ToCreatedHttpResult();
89+
})
90+
.RequireAuthorization(Permissions.Page_Edit)
91+
.WithName("CreateKnowledgePartner");
92+
93+
about.MapPut("/knowledge-partners/{id:guid}", async (
94+
System.Guid id,
95+
UpdateKnowledgePartnerRequest body,
96+
IMediator mediator, CancellationToken ct) =>
97+
{
98+
var cmd = new UpdateKnowledgePartnerCommand(
99+
id, body.NameAr, body.NameEn, body.LogoUrl, body.WebsiteUrl,
100+
body.DescriptionAr, body.DescriptionEn);
101+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
102+
return result.ToHttpResult();
103+
})
104+
.RequireAuthorization(Permissions.Page_Edit)
105+
.WithName("UpdateKnowledgePartner");
106+
107+
about.MapDelete("/knowledge-partners/{id:guid}", async (
108+
System.Guid id,
109+
IMediator mediator, CancellationToken ct) =>
110+
{
111+
var result = await mediator.Send(new DeleteKnowledgePartnerCommand(id), ct).ConfigureAwait(false);
112+
return result.ToNoContentHttpResult();
113+
})
114+
.RequireAuthorization(Permissions.Page_Edit)
115+
.WithName("DeleteKnowledgePartner");
116+
117+
return app;
118+
}
119+
}
120+
121+
public sealed record UpdateAboutSettingsRequest(
122+
string DescriptionAr,
123+
string DescriptionEn,
124+
string? HowToUseVideoUrl,
125+
string RowVersion);
126+
127+
public sealed record CreateGlossaryEntryRequest(
128+
string TermAr,
129+
string TermEn,
130+
string DefinitionAr,
131+
string DefinitionEn);
132+
133+
public sealed record UpdateGlossaryEntryRequest(
134+
string TermAr,
135+
string TermEn,
136+
string DefinitionAr,
137+
string DefinitionEn);
138+
139+
public sealed record CreateKnowledgePartnerRequest(
140+
string NameAr,
141+
string NameEn,
142+
string? LogoUrl,
143+
string? WebsiteUrl,
144+
string? DescriptionAr,
145+
string? DescriptionEn);
146+
147+
public sealed record UpdateKnowledgePartnerRequest(
148+
string NameAr,
149+
string NameEn,
150+
string? LogoUrl,
151+
string? WebsiteUrl,
152+
string? DescriptionAr,
153+
string? DescriptionEn);
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using CCE.Api.Common.Extensions;
2+
using CCE.Application.Common;
3+
using CCE.Application.PlatformSettings.Commands.UpdateHomepageSettings;
4+
using CCE.Application.PlatformSettings.Queries.GetHomepageSettings;
5+
using CCE.Domain;
6+
using MediatR;
7+
using Microsoft.AspNetCore.Builder;
8+
using Microsoft.AspNetCore.Http;
9+
using Microsoft.AspNetCore.Routing;
10+
11+
namespace CCE.Api.Internal.Endpoints;
12+
13+
public static class HomepageSettingsEndpoints
14+
{
15+
public static IEndpointRouteBuilder MapHomepageSettingsEndpoints(this IEndpointRouteBuilder app)
16+
{
17+
var settings = app.MapGroup("/api/admin/settings/homepage").WithTags("PlatformSettings");
18+
19+
settings.MapGet("", async (IMediator mediator, CancellationToken ct) =>
20+
{
21+
var result = await mediator.Send(new GetHomepageSettingsQuery(), ct).ConfigureAwait(false);
22+
return result.ToHttpResult();
23+
})
24+
.RequireAuthorization(Permissions.Page_Edit)
25+
.WithName("GetHomepageSettings");
26+
27+
settings.MapPut("", async (UpdateHomepageSettingsRequest body, IMediator mediator, CancellationToken ct) =>
28+
{
29+
var rowVersion = string.IsNullOrEmpty(body.RowVersion)
30+
? System.Array.Empty<byte>()
31+
: System.Convert.FromBase64String(body.RowVersion);
32+
var cmd = new UpdateHomepageSettingsCommand(
33+
body.VideoUrl,
34+
body.ObjectiveAr,
35+
body.ObjectiveEn,
36+
body.CceConceptsAr,
37+
body.CceConceptsEn,
38+
body.ParticipatingCountryIds,
39+
rowVersion);
40+
var result = await mediator.Send(cmd, ct).ConfigureAwait(false);
41+
return result.ToHttpResult();
42+
})
43+
.RequireAuthorization(Permissions.Page_Edit)
44+
.WithName("UpdateHomepageSettings");
45+
46+
return app;
47+
}
48+
}
49+
50+
public sealed record UpdateHomepageSettingsRequest(
51+
string? VideoUrl,
52+
string ObjectiveAr,
53+
string ObjectiveEn,
54+
string CceConceptsAr,
55+
string CceConceptsEn,
56+
System.Collections.Generic.IReadOnlyList<System.Guid> ParticipatingCountryIds,
57+
string RowVersion);

0 commit comments

Comments
 (0)