|
| 1 | +# PlatformSettings Database Structure |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +There are **3 singleton parent tables** and **4 child/collection tables**. |
| 6 | +All singleton parents support soft delete. Child collection tables do **not** |
| 7 | +have soft-delete columns (hard delete only). |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Singleton Parent Tables (1 row each) |
| 12 | + |
| 13 | +### `homepage_settings` |
| 14 | + |
| 15 | +| Column | Type | Single / List | How Populated | |
| 16 | +|---|---|---|---| |
| 17 | +| `id` | `uniqueidentifier` PK | Single | `ReferenceDataSeeder` creates, `PlatformSettingsSeeder` enriches | |
| 18 | +| `objective_ar` | `nvarchar(1000)` | Single | Seeder + Admin API `PUT /api/admin/settings/homepage` | |
| 19 | +| `objective_en` | `nvarchar(1000)` | Single | Seeder + Admin API | |
| 20 | +| `video_url` | `nvarchar(max)` | Single | Seeder + Admin API | |
| 21 | +| `cce_concepts_ar` | `nvarchar(max)` | Single | Seeder + Admin API | |
| 22 | +| `cce_concepts_en` | `nvarchar(max)` | Single | Seeder + Admin API | |
| 23 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single | Auto | |
| 24 | +| `deleted_by_id`, `deleted_on`, `is_deleted` | soft delete | Single | Auto | |
| 25 | +| `row_version` | `rowversion` | Single | Auto (concurrency) | |
| 26 | + |
| 27 | +**LocalizedText mapping:** `Objective` → `objective_ar` / `objective_en` |
| 28 | + |
| 29 | +--- |
| 30 | + |
| 31 | +### `about_settings` |
| 32 | + |
| 33 | +| Column | Type | Single / List | How Populated | |
| 34 | +|---|---|---|---| |
| 35 | +| `id` | `uniqueidentifier` PK | Single | `ReferenceDataSeeder` creates, `PlatformSettingsSeeder` enriches | |
| 36 | +| `description_ar` | `nvarchar(1000)` | Single | Seeder + Admin API `PUT /api/admin/settings/about` | |
| 37 | +| `description_en` | `nvarchar(1000)` | Single | Seeder + Admin API | |
| 38 | +| `how_to_use_video_url` | `nvarchar(max)` | Single | Seeder + Admin API | |
| 39 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single | Auto | |
| 40 | +| `deleted_by_id`, `deleted_on`, `is_deleted` | soft delete | Single | Auto | |
| 41 | +| `row_version` | `rowversion` | Single | Auto (concurrency) | |
| 42 | + |
| 43 | +**LocalizedText mapping:** `Description` → `description_ar` / `description_en` |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +### `policies_settings` |
| 48 | + |
| 49 | +| Column | Type | Single / List | How Populated | |
| 50 | +|---|---|---|---| |
| 51 | +| `id` | `uniqueidentifier` PK | Single | `ReferenceDataSeeder` creates bare row | |
| 52 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single | Auto | |
| 53 | +| `deleted_by_id`, `deleted_on`, `is_deleted` | soft delete | Single | Auto | |
| 54 | +| `row_version` | `rowversion` | Single | Auto (concurrency) | |
| 55 | + |
| 56 | +**Note:** No admin endpoint updates this table directly. It is managed |
| 57 | +indirectly through its child `policy_sections`. |
| 58 | + |
| 59 | +--- |
| 60 | + |
| 61 | +## Child / Collection Tables (0..N rows per parent) |
| 62 | + |
| 63 | +### `homepage_countries` — **List** of country links |
| 64 | + |
| 65 | +| Column | Type | Single / List | How Populated | |
| 66 | +|---|---|---|---| |
| 67 | +| `id` | `uniqueidentifier` PK | Single per row | Seeder + Admin API | |
| 68 | +| `homepage_settings_id` | `uniqueidentifier` FK | Single per row | Set by `SyncCountries()` domain method | |
| 69 | +| `country_id` | `uniqueidentifier` | Single per row | Seeder + Admin API | |
| 70 | +| `order_index` | `int` | Single per row | Auto (0, 1, 2...) | |
| 71 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single per row | Auto | |
| 72 | + |
| 73 | +**Populated by:** |
| 74 | +- **Seeder:** `PlatformSettingsSeeder` adds 5 GCC countries (SAU, ARE, KWT, QAT, BHR) |
| 75 | +- **Admin API:** `PUT /api/admin/settings/homepage` sends `ParticipatingCountryIds: ["guid", "guid"]` → `SyncCountries()` adds/removes/reorders |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +### `glossary_entries` — **List** of entries |
| 80 | + |
| 81 | +| Column | Type | Single / List | How Populated | |
| 82 | +|---|---|---|---| |
| 83 | +| `id` | `uniqueidentifier` PK | Single per row | Seeder + Admin API | |
| 84 | +| `about_settings_id` | `uniqueidentifier` FK | Single per row | Set by `AddGlossaryEntry()` | |
| 85 | +| `term_ar` | `nvarchar(100)` | Single per row | Seeder + Admin API | |
| 86 | +| `term_en` | `nvarchar(100)` | Single per row | Seeder + Admin API | |
| 87 | +| `definition_ar` | `nvarchar(1000)` | Single per row | Seeder + Admin API | |
| 88 | +| `definition_en` | `nvarchar(1000)` | Single per row | Seeder + Admin API | |
| 89 | +| `order_index` | `int` | Single per row | Auto | |
| 90 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single per row | Auto | |
| 91 | + |
| 92 | +**LocalizedText mappings:** |
| 93 | +- `Term` → `term_ar` / `term_en` |
| 94 | +- `Definition` → `definition_ar` / `definition_en` |
| 95 | + |
| 96 | +**Populated by:** |
| 97 | +- **Seeder:** `PlatformSettingsSeeder` adds 4 entries (CCE, DAC, CCUS, LCOE) |
| 98 | +- **Admin API:** |
| 99 | + - `POST /api/admin/settings/about/glossary` |
| 100 | + - `PUT /api/admin/settings/about/glossary/{id}` |
| 101 | + - `DELETE /api/admin/settings/about/glossary/{id}` |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +### `knowledge_partners` — **List** of partners |
| 106 | + |
| 107 | +| Column | Type | Single / List | How Populated | |
| 108 | +|---|---|---|---| |
| 109 | +| `id` | `uniqueidentifier` PK | Single per row | Seeder + Admin API | |
| 110 | +| `about_settings_id` | `uniqueidentifier` FK | Single per row | Set by `AddKnowledgePartner()` | |
| 111 | +| `name_ar` | `nvarchar(200)` | Single per row | Seeder + Admin API | |
| 112 | +| `name_en` | `nvarchar(200)` | Single per row | Seeder + Admin API | |
| 113 | +| `description_ar` | `nvarchar(1000)` | Single per row | Seeder + Admin API | |
| 114 | +| `description_en` | `nvarchar(1000)` | Single per row | Seeder + Admin API | |
| 115 | +| `logo_url` | `nvarchar(max)` | Single per row | Seeder + Admin API | |
| 116 | +| `website_url` | `nvarchar(max)` | Single per row | Seeder + Admin API | |
| 117 | +| `order_index` | `int` | Single per row | Auto | |
| 118 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single per row | Auto | |
| 119 | + |
| 120 | +**LocalizedText mappings:** |
| 121 | +- `Name` → `name_ar` / `name_en` |
| 122 | +- `Description` → `description_ar` / `description_en` |
| 123 | + |
| 124 | +**Populated by:** |
| 125 | +- **Seeder:** `PlatformSettingsSeeder` adds 3 partners (KAPSARC, IRENA, GCEP) |
| 126 | +- **Admin API:** |
| 127 | + - `POST /api/admin/settings/about/knowledge-partners` |
| 128 | + - `PUT /api/admin/settings/about/knowledge-partners/{id}` |
| 129 | + - `DELETE /api/admin/settings/about/knowledge-partners/{id}` |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +### `policy_sections` — **List** of sections |
| 134 | + |
| 135 | +| Column | Type | Single / List | How Populated | |
| 136 | +|---|---|---|---| |
| 137 | +| `id` | `uniqueidentifier` PK | Single per row | Seeder + Admin API | |
| 138 | +| `policies_settings_id` | `uniqueidentifier` FK | Single per row | Set by `AddSection()` | |
| 139 | +| `type` | `int` (enum) | Single per row | Seeder + Admin API | |
| 140 | +| `title_ar` | `nvarchar(500)` | Single per row | Seeder + Admin API | |
| 141 | +| `title_en` | `nvarchar(500)` | Single per row | Seeder + Admin API | |
| 142 | +| `content_ar` | `nvarchar(max)` | Single per row | Seeder + Admin API | |
| 143 | +| `content_en` | `nvarchar(max)` | Single per row | Seeder + Admin API | |
| 144 | +| `order_index` | `int` | Single per row | Auto | |
| 145 | +| `created_by_id`, `created_on`, `last_modified_by_id`, `last_modified_on` | audit | Single per row | Auto | |
| 146 | + |
| 147 | +**LocalizedText mappings:** |
| 148 | +- `Title` → `title_ar` / `title_en` |
| 149 | +- `Content` → `content_ar` / `content_en` |
| 150 | + |
| 151 | +**Populated by:** |
| 152 | +- **Seeder:** `PlatformSettingsSeeder` adds 3 sections (Terms, Privacy, FAQ) |
| 153 | +- **Admin API:** |
| 154 | + - `POST /api/admin/settings/policies/sections` |
| 155 | + - `PUT /api/admin/settings/policies/sections/{id}` |
| 156 | + - `PUT /api/admin/settings/policies/sections/{id}/order` |
| 157 | + - `DELETE /api/admin/settings/policies/sections/{id}` |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +## Key Relationships |
| 162 | + |
| 163 | +| Child Table | FK Column | Parent Table | Delete Behavior | |
| 164 | +|---|---|---|---| |
| 165 | +| `homepage_countries` | `homepage_settings_id` | `homepage_settings` | Cascade | |
| 166 | +| `glossary_entries` | `about_settings_id` | `about_settings` | Cascade | |
| 167 | +| `knowledge_partners` | `about_settings_id` | `about_settings` | Cascade | |
| 168 | +| `policy_sections` | `policies_settings_id` | `policies_settings` | Cascade | |
| 169 | + |
| 170 | +`homepage_countries.country_id` is a **logical reference** to the `countries` |
| 171 | +table; there is no database-enforced foreign key constraint. |
| 172 | + |
| 173 | +--- |
| 174 | + |
| 175 | +## LocalizedText Column Mappings |
| 176 | + |
| 177 | +Every bilingual field is stored as two columns (`_ar` / `_en`) via EF Core |
| 178 | +owned entities (`OwnsOne`): |
| 179 | + |
| 180 | +| Table | Property | AR Column | EN Column | Max Length | |
| 181 | +|---|---|---|---|---| |
| 182 | +| `homepage_settings` | `Objective` | `objective_ar` | `objective_en` | 1000 | |
| 183 | +| `about_settings` | `Description` | `description_ar` | `description_en` | 1000 | |
| 184 | +| `glossary_entries` | `Term` | `term_ar` | `term_en` | 100 | |
| 185 | +| `glossary_entries` | `Definition` | `definition_ar` | `definition_en` | 1000 | |
| 186 | +| `knowledge_partners` | `Name` | `name_ar` | `name_en` | 200 | |
| 187 | +| `knowledge_partners` | `Description` | `description_ar` | `description_en` | 1000 | |
| 188 | +| `policy_sections` | `Title` | `title_ar` | `title_en` | 500 | |
| 189 | +| `policy_sections` | `Content` | `content_ar` | `content_en` | max | |
| 190 | + |
| 191 | +--- |
| 192 | + |
| 193 | +## Public API Read Models |
| 194 | + |
| 195 | +- **Homepage:** Returns `VideoUrl`, `Objective` (ar/en), `CceConceptsAr`, |
| 196 | + `CceConceptsEn`, linked `Countries` (joined with `countries` table for |
| 197 | + name/flag/ISO), and active `HomepageSections` (from the separate |
| 198 | + `homepage_sections` content table). |
| 199 | + |
| 200 | +- **About:** Returns `Description` (ar/en), `HowToUseVideoUrl`, ordered |
| 201 | + `GlossaryEntries`, and ordered `KnowledgePartners`. |
| 202 | + |
| 203 | +- **Policies:** Returns ordered `PolicySections` with `Type`, `Title` (ar/en), |
| 204 | + and `Content` (ar/en) — currently as **single HTML strings**. |
| 205 | + |
| 206 | +--- |
| 207 | + |
| 208 | +## The Problem |
| 209 | + |
| 210 | +`policy_sections.content_ar` and `policy_sections.content_en` are currently |
| 211 | +**Single values** (one big HTML string per section). You want them to become |
| 212 | +a **List** so the API returns: |
| 213 | + |
| 214 | +```json |
| 215 | +{ |
| 216 | + "contentItems": [ |
| 217 | + { "ar": "1. القبول بالشروط", "en": "1. Acceptance of Terms" }, |
| 218 | + { "ar": "باستخدامك لهذه المنصة...", "en": "By using this platform..." } |
| 219 | + ] |
| 220 | +} |
| 221 | +``` |
| 222 | + |
| 223 | +This would require a **new child table** following the exact same pattern as |
| 224 | +`glossary_entries` and `knowledge_partners`. |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +## Related Files |
| 229 | + |
| 230 | +| Layer | Path | |
| 231 | +|---|---| |
| 232 | +| Domain | `src/CCE.Domain/PlatformSettings/` | |
| 233 | +| EF Config | `src/CCE.Infrastructure/Persistence/Configurations/PlatformSettings/` | |
| 234 | +| Migrations | `src/CCE.Infrastructure/Persistence/Migrations/` | |
| 235 | +| Commands | `src/CCE.Application/PlatformSettings/Commands/` | |
| 236 | +| Queries | `src/CCE.Application/PlatformSettings/Queries/` | |
| 237 | +| Public Queries | `src/CCE.Application/PlatformSettings/Public/Queries/` | |
| 238 | +| Internal API | `src/CCE.Api.Internal/Endpoints/` | |
| 239 | +| External API | `src/CCE.Api.External/Endpoints/` | |
| 240 | +| Seeders | `src/CCE.Seeder/Seeders/` | |
0 commit comments