From f8206f11668e796936637e0faef512d45107e7c7 Mon Sep 17 00:00:00 2001 From: ahmed Date: Mon, 25 May 2026 14:16:58 +0300 Subject: [PATCH 1/5] feat: add service evaluation feature including endpoints, application logic, and migration --- backend/AddServiceEvaluation.sql | 1428 +++++++ .../Localization/Resources.yaml | 8 + .../Endpoints/EvaluationEndpoints.cs | 42 + backend/src/CCE.Api.External/Program.cs | 1 + .../Endpoints/EvaluationEndpoints.cs | 42 + backend/src/CCE.Api.Internal/Program.cs | 1 + backend/src/CCE.Application/Common/Errors.cs | 3 + .../Common/Interfaces/ICceDbContext.cs | 4 + .../Errors/ApplicationErrors.cs | 6 + .../SubmitEvaluationCommand.cs | 11 + .../SubmitEvaluationCommandHandler.cs | 49 + .../SubmitEvaluationCommandValidator.cs | 15 + .../Evaluation/DTOs/ServiceEvaluationDto.cs | 13 + .../Evaluation/IEvaluationRepository.cs | 10 + .../GetAllEvaluationsQuery.cs | 6 + .../GetAllEvaluationsQueryHandler.cs | 32 + .../GetEvaluationByIdQuery.cs | 6 + .../GetEvaluationByIdQueryHandler.cs | 34 + .../Messages/MessageFactory.cs | 5 + .../CCE.Application/Messages/SystemCode.cs | 3 + .../CCE.Application/Messages/SystemCodeMap.cs | 6 + .../CCE.Domain/Evaluation/EvaluationRating.cs | 11 + .../Evaluation/ServiceEvaluation.cs | 61 + .../CCE.Infrastructure/DependencyInjection.cs | 5 + .../Evaluation/EvaluationRepository.cs | 37 + .../Persistence/CceDbContext.cs | 5 + .../ServiceEvaluationConfiguration.cs | 35 + ...525092623_AddServiceEvaluation.Designer.cs | 3758 +++++++++++++++++ .../20260525092623_AddServiceEvaluation.cs | 47 + .../Migrations/CceDbContextModelSnapshot.cs | 53 + 30 files changed, 5737 insertions(+) create mode 100644 backend/AddServiceEvaluation.sql create mode 100644 backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs create mode 100644 backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs create mode 100644 backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs create mode 100644 backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs create mode 100644 backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs create mode 100644 backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs create mode 100644 backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs create mode 100644 backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs create mode 100644 backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs create mode 100644 backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs create mode 100644 backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs create mode 100644 backend/src/CCE.Domain/Evaluation/EvaluationRating.cs create mode 100644 backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs create mode 100644 backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs create mode 100644 backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs diff --git a/backend/AddServiceEvaluation.sql b/backend/AddServiceEvaluation.sql new file mode 100644 index 00000000..e4728d13 --- /dev/null +++ b/backend/AddServiceEvaluation.sql @@ -0,0 +1,1428 @@ +IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL +BEGIN + CREATE TABLE [__EFMigrationsHistory] ( + [migration_id] nvarchar(150) NOT NULL, + [product_version] nvarchar(32) NOT NULL, + CONSTRAINT [pk___ef_migrations_history] PRIMARY KEY ([migration_id]) + ); +END; +GO + +BEGIN TRANSACTION; +CREATE TABLE [audit_events] ( + [id] uniqueidentifier NOT NULL, + [occurred_on] datetimeoffset NOT NULL, + [actor] nvarchar(256) NOT NULL, + [action] nvarchar(128) NOT NULL, + [resource] nvarchar(512) NOT NULL, + [correlation_id] uniqueidentifier NOT NULL, + [diff] nvarchar(max) NULL, + CONSTRAINT [pk_audit_events] PRIMARY KEY ([id]) +); + +CREATE INDEX [ix_audit_events_actor_occurred_on] ON [audit_events] ([actor], [occurred_on]); + +CREATE INDEX [ix_audit_events_correlation_id] ON [audit_events] ([correlation_id]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260425134009_InitialAuditEvents', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; + +CREATE TRIGGER trg_audit_events_no_update_delete +ON dbo.audit_events +INSTEAD OF UPDATE, DELETE +AS +BEGIN + THROW 51000, 'audit_events is append-only; UPDATE and DELETE are not permitted.', 1; +END; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260425134559_AuditEventsAppendOnlyTrigger', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +CREATE TABLE [AspNetRoles] ( + [id] uniqueidentifier NOT NULL, + [name] nvarchar(256) NULL, + [normalized_name] nvarchar(256) NULL, + [concurrency_stamp] nvarchar(max) NULL, + CONSTRAINT [pk_asp_net_roles] PRIMARY KEY ([id]) +); + +CREATE TABLE [AspNetUsers] ( + [id] uniqueidentifier NOT NULL, + [locale_preference] nvarchar(2) NOT NULL, + [knowledge_level] int NOT NULL, + [interests] nvarchar(max) NOT NULL, + [country_id] uniqueidentifier NULL, + [avatar_url] nvarchar(2048) NULL, + [user_name] nvarchar(256) NULL, + [normalized_user_name] nvarchar(256) NULL, + [email] nvarchar(256) NULL, + [normalized_email] nvarchar(256) NULL, + [email_confirmed] bit NOT NULL, + [password_hash] nvarchar(max) NULL, + [security_stamp] nvarchar(max) NULL, + [concurrency_stamp] nvarchar(max) NULL, + [phone_number] nvarchar(max) NULL, + [phone_number_confirmed] bit NOT NULL, + [two_factor_enabled] bit NOT NULL, + [lockout_end] datetimeoffset NULL, + [lockout_enabled] bit NOT NULL, + [access_failed_count] int NOT NULL, + CONSTRAINT [pk_asp_net_users] PRIMARY KEY ([id]) +); + +CREATE TABLE [asset_files] ( + [id] uniqueidentifier NOT NULL, + [url] nvarchar(2048) NOT NULL, + [original_file_name] nvarchar(512) NOT NULL, + [size_bytes] bigint NOT NULL, + [mime_type] nvarchar(128) NOT NULL, + [uploaded_by_id] uniqueidentifier NOT NULL, + [uploaded_on] datetimeoffset NOT NULL, + [virus_scan_status] int NOT NULL, + [scanned_on] datetimeoffset NULL, + CONSTRAINT [pk_asset_files] PRIMARY KEY ([id]) +); + +CREATE TABLE [city_scenario_results] ( + [id] uniqueidentifier NOT NULL, + [scenario_id] uniqueidentifier NOT NULL, + [computed_carbon_neutrality_year] int NULL, + [computed_total_cost_usd] decimal(18,2) NOT NULL, + [computed_at] datetimeoffset NOT NULL, + [engine_version] nvarchar(64) NOT NULL, + CONSTRAINT [pk_city_scenario_results] PRIMARY KEY ([id]) +); + +CREATE TABLE [city_scenarios] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [city_type] int NOT NULL, + [target_year] int NOT NULL, + [configuration_json] nvarchar(max) NOT NULL, + [created_on] datetimeoffset NOT NULL, + [last_modified_on] datetimeoffset NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_city_scenarios] PRIMARY KEY ([id]) +); + +CREATE TABLE [city_technologies] ( + [id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [category_ar] nvarchar(128) NOT NULL, + [category_en] nvarchar(128) NOT NULL, + [carbon_impact_kg_per_year] decimal(18,2) NOT NULL, + [cost_usd] decimal(18,2) NOT NULL, + [icon_url] nvarchar(2048) NULL, + [is_active] bit NOT NULL, + CONSTRAINT [pk_city_technologies] PRIMARY KEY ([id]) +); + +CREATE TABLE [countries] ( + [id] uniqueidentifier NOT NULL, + [iso_alpha3] nvarchar(3) NOT NULL, + [iso_alpha2] nvarchar(2) NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [region_ar] nvarchar(128) NOT NULL, + [region_en] nvarchar(128) NOT NULL, + [flag_url] nvarchar(2048) NOT NULL, + [latest_kapsarc_snapshot_id] uniqueidentifier NULL, + [is_active] bit NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_countries] PRIMARY KEY ([id]) +); + +CREATE TABLE [country_kapsarc_snapshots] ( + [id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NOT NULL, + [classification] nvarchar(64) NOT NULL, + [performance_score] decimal(5,2) NOT NULL, + [total_index] decimal(5,2) NOT NULL, + [snapshot_taken_on] datetimeoffset NOT NULL, + [source_version] nvarchar(32) NULL, + CONSTRAINT [pk_country_kapsarc_snapshots] PRIMARY KEY ([id]) +); + +CREATE TABLE [country_profiles] ( + [id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [key_initiatives_ar] nvarchar(max) NOT NULL, + [key_initiatives_en] nvarchar(max) NOT NULL, + [contact_info_ar] nvarchar(2000) NULL, + [contact_info_en] nvarchar(2000) NULL, + [last_updated_by_id] uniqueidentifier NOT NULL, + [last_updated_on] datetimeoffset NOT NULL, + [row_version] rowversion NOT NULL, + CONSTRAINT [pk_country_profiles] PRIMARY KEY ([id]) +); + +CREATE TABLE [country_resource_requests] ( + [id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NOT NULL, + [requested_by_id] uniqueidentifier NOT NULL, + [status] int NOT NULL, + [proposed_title_ar] nvarchar(512) NOT NULL, + [proposed_title_en] nvarchar(512) NOT NULL, + [proposed_description_ar] nvarchar(max) NOT NULL, + [proposed_description_en] nvarchar(max) NOT NULL, + [proposed_resource_type] int NOT NULL, + [proposed_asset_file_id] uniqueidentifier NOT NULL, + [submitted_on] datetimeoffset NOT NULL, + [admin_notes_ar] nvarchar(2000) NULL, + [admin_notes_en] nvarchar(2000) NULL, + [processed_by_id] uniqueidentifier NULL, + [processed_on] datetimeoffset NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_country_resource_requests] PRIMARY KEY ([id]) +); + +CREATE TABLE [events] ( + [id] uniqueidentifier NOT NULL, + [title_ar] nvarchar(512) NOT NULL, + [title_en] nvarchar(512) NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [starts_on] datetimeoffset NOT NULL, + [ends_on] datetimeoffset NOT NULL, + [location_ar] nvarchar(512) NULL, + [location_en] nvarchar(512) NULL, + [online_meeting_url] nvarchar(2048) NULL, + [featured_image_url] nvarchar(2048) NULL, + [i_cal_uid] nvarchar(256) NOT NULL, + [row_version] rowversion NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_events] PRIMARY KEY ([id]) +); + +CREATE TABLE [expert_profiles] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [bio_ar] nvarchar(2000) NOT NULL, + [bio_en] nvarchar(2000) NOT NULL, + [expertise_tags] nvarchar(max) NOT NULL, + [academic_title_ar] nvarchar(128) NOT NULL, + [academic_title_en] nvarchar(128) NOT NULL, + [approved_on] datetimeoffset NOT NULL, + [approved_by_id] uniqueidentifier NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_expert_profiles] PRIMARY KEY ([id]) +); + +CREATE TABLE [expert_registration_requests] ( + [id] uniqueidentifier NOT NULL, + [requested_by_id] uniqueidentifier NOT NULL, + [requested_bio_ar] nvarchar(2000) NOT NULL, + [requested_bio_en] nvarchar(2000) NOT NULL, + [requested_tags] nvarchar(max) NOT NULL, + [submitted_on] datetimeoffset NOT NULL, + [status] int NOT NULL, + [processed_by_id] uniqueidentifier NULL, + [processed_on] datetimeoffset NULL, + [rejection_reason_ar] nvarchar(1000) NULL, + [rejection_reason_en] nvarchar(1000) NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_expert_registration_requests] PRIMARY KEY ([id]) +); + +CREATE TABLE [homepage_sections] ( + [id] uniqueidentifier NOT NULL, + [section_type] int NOT NULL, + [order_index] int NOT NULL, + [content_ar] nvarchar(max) NOT NULL, + [content_en] nvarchar(max) NOT NULL, + [is_active] bit NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_homepage_sections] PRIMARY KEY ([id]) +); + +CREATE TABLE [knowledge_map_associations] ( + [id] uniqueidentifier NOT NULL, + [node_id] uniqueidentifier NOT NULL, + [associated_type] int NOT NULL, + [associated_id] uniqueidentifier NOT NULL, + [order_index] int NOT NULL, + CONSTRAINT [pk_knowledge_map_associations] PRIMARY KEY ([id]) +); + +CREATE TABLE [knowledge_map_edges] ( + [id] uniqueidentifier NOT NULL, + [map_id] uniqueidentifier NOT NULL, + [from_node_id] uniqueidentifier NOT NULL, + [to_node_id] uniqueidentifier NOT NULL, + [relationship_type] int NOT NULL, + [order_index] int NOT NULL, + CONSTRAINT [pk_knowledge_map_edges] PRIMARY KEY ([id]) +); + +CREATE TABLE [knowledge_map_nodes] ( + [id] uniqueidentifier NOT NULL, + [map_id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [node_type] int NOT NULL, + [description_ar] nvarchar(max) NULL, + [description_en] nvarchar(max) NULL, + [icon_url] nvarchar(2048) NULL, + [layout_x] float NOT NULL, + [layout_y] float NOT NULL, + [order_index] int NOT NULL, + CONSTRAINT [pk_knowledge_map_nodes] PRIMARY KEY ([id]) +); + +CREATE TABLE [knowledge_maps] ( + [id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [slug] nvarchar(128) NOT NULL, + [is_active] bit NOT NULL, + [row_version] rowversion NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_knowledge_maps] PRIMARY KEY ([id]) +); + +CREATE TABLE [news] ( + [id] uniqueidentifier NOT NULL, + [title_ar] nvarchar(512) NOT NULL, + [title_en] nvarchar(512) NOT NULL, + [content_ar] nvarchar(max) NOT NULL, + [content_en] nvarchar(max) NOT NULL, + [slug] nvarchar(256) NOT NULL, + [author_id] uniqueidentifier NOT NULL, + [featured_image_url] nvarchar(2048) NULL, + [published_on] datetimeoffset NULL, + [is_featured] bit NOT NULL, + [row_version] rowversion NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_news] PRIMARY KEY ([id]) +); + +CREATE TABLE [newsletter_subscriptions] ( + [id] uniqueidentifier NOT NULL, + [email] nvarchar(320) NOT NULL, + [locale_preference] nvarchar(2) NOT NULL, + [is_confirmed] bit NOT NULL, + [confirmation_token] nvarchar(64) NOT NULL, + [confirmed_on] datetimeoffset NULL, + [unsubscribed_on] datetimeoffset NULL, + CONSTRAINT [pk_newsletter_subscriptions] PRIMARY KEY ([id]) +); + +CREATE TABLE [notification_templates] ( + [id] uniqueidentifier NOT NULL, + [code] nvarchar(64) NOT NULL, + [subject_ar] nvarchar(512) NOT NULL, + [subject_en] nvarchar(512) NOT NULL, + [body_ar] nvarchar(max) NOT NULL, + [body_en] nvarchar(max) NOT NULL, + [channel] int NOT NULL, + [variable_schema_json] nvarchar(max) NOT NULL, + [is_active] bit NOT NULL, + CONSTRAINT [pk_notification_templates] PRIMARY KEY ([id]) +); + +CREATE TABLE [pages] ( + [id] uniqueidentifier NOT NULL, + [slug] nvarchar(256) NOT NULL, + [page_type] int NOT NULL, + [title_ar] nvarchar(512) NOT NULL, + [title_en] nvarchar(512) NOT NULL, + [content_ar] nvarchar(max) NOT NULL, + [content_en] nvarchar(max) NOT NULL, + [row_version] rowversion NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_pages] PRIMARY KEY ([id]) +); + +CREATE TABLE [post_follows] ( + [id] uniqueidentifier NOT NULL, + [post_id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [followed_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_post_follows] PRIMARY KEY ([id]) +); + +CREATE TABLE [post_ratings] ( + [id] uniqueidentifier NOT NULL, + [post_id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [stars] int NOT NULL, + [rated_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_post_ratings] PRIMARY KEY ([id]) +); + +CREATE TABLE [post_replies] ( + [id] uniqueidentifier NOT NULL, + [post_id] uniqueidentifier NOT NULL, + [author_id] uniqueidentifier NOT NULL, + [content] nvarchar(max) NOT NULL, + [locale] nvarchar(2) NOT NULL, + [parent_reply_id] uniqueidentifier NULL, + [is_by_expert] bit NOT NULL, + [created_on] datetimeoffset NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_post_replies] PRIMARY KEY ([id]) +); + +CREATE TABLE [posts] ( + [id] uniqueidentifier NOT NULL, + [topic_id] uniqueidentifier NOT NULL, + [author_id] uniqueidentifier NOT NULL, + [content] nvarchar(max) NOT NULL, + [locale] nvarchar(2) NOT NULL, + [is_answerable] bit NOT NULL, + [answered_reply_id] uniqueidentifier NULL, + [created_on] datetimeoffset NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_posts] PRIMARY KEY ([id]) +); + +CREATE TABLE [resource_categories] ( + [id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [slug] nvarchar(128) NOT NULL, + [parent_id] uniqueidentifier NULL, + [order_index] int NOT NULL, + [is_active] bit NOT NULL, + CONSTRAINT [pk_resource_categories] PRIMARY KEY ([id]) +); + +CREATE TABLE [resources] ( + [id] uniqueidentifier NOT NULL, + [title_ar] nvarchar(512) NOT NULL, + [title_en] nvarchar(512) NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [resource_type] int NOT NULL, + [category_id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NULL, + [uploaded_by_id] uniqueidentifier NOT NULL, + [asset_file_id] uniqueidentifier NOT NULL, + [published_on] datetimeoffset NULL, + [view_count] bigint NOT NULL, + [row_version] rowversion NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_resources] PRIMARY KEY ([id]) +); + +CREATE TABLE [search_query_logs] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NULL, + [query_text] nvarchar(1000) NOT NULL, + [results_count] int NOT NULL, + [response_time_ms] int NOT NULL, + [locale] nvarchar(2) NOT NULL, + [submitted_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_search_query_logs] PRIMARY KEY ([id]) +); + +CREATE TABLE [service_ratings] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NULL, + [rating] int NOT NULL, + [comment_ar] nvarchar(2000) NULL, + [comment_en] nvarchar(2000) NULL, + [page] nvarchar(256) NOT NULL, + [locale] nvarchar(2) NOT NULL, + [submitted_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_service_ratings] PRIMARY KEY ([id]) +); + +CREATE TABLE [state_representative_assignments] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NOT NULL, + [assigned_on] datetimeoffset NOT NULL, + [assigned_by_id] uniqueidentifier NOT NULL, + [revoked_on] datetimeoffset NULL, + [revoked_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_state_representative_assignments] PRIMARY KEY ([id]) +); + +CREATE TABLE [topic_follows] ( + [id] uniqueidentifier NOT NULL, + [topic_id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [followed_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_topic_follows] PRIMARY KEY ([id]) +); + +CREATE TABLE [topics] ( + [id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(256) NOT NULL, + [name_en] nvarchar(256) NOT NULL, + [description_ar] nvarchar(max) NOT NULL, + [description_en] nvarchar(max) NOT NULL, + [slug] nvarchar(128) NOT NULL, + [parent_id] uniqueidentifier NULL, + [icon_url] nvarchar(2048) NULL, + [order_index] int NOT NULL, + [is_active] bit NOT NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_topics] PRIMARY KEY ([id]) +); + +CREATE TABLE [user_follows] ( + [id] uniqueidentifier NOT NULL, + [follower_id] uniqueidentifier NOT NULL, + [followed_id] uniqueidentifier NOT NULL, + [followed_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_user_follows] PRIMARY KEY ([id]) +); + +CREATE TABLE [user_notifications] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [template_id] uniqueidentifier NOT NULL, + [rendered_subject_ar] nvarchar(512) NOT NULL, + [rendered_subject_en] nvarchar(512) NOT NULL, + [rendered_body] nvarchar(max) NOT NULL, + [rendered_locale] nvarchar(2) NOT NULL, + [channel] int NOT NULL, + [sent_on] datetimeoffset NULL, + [read_on] datetimeoffset NULL, + [status] int NOT NULL, + CONSTRAINT [pk_user_notifications] PRIMARY KEY ([id]) +); + +CREATE TABLE [AspNetRoleClaims] ( + [id] int NOT NULL IDENTITY, + [role_id] uniqueidentifier NOT NULL, + [claim_type] nvarchar(max) NULL, + [claim_value] nvarchar(max) NULL, + CONSTRAINT [pk_asp_net_role_claims] PRIMARY KEY ([id]), + CONSTRAINT [fk_asp_net_role_claims_asp_net_roles_role_id] FOREIGN KEY ([role_id]) REFERENCES [AspNetRoles] ([id]) ON DELETE CASCADE +); + +CREATE TABLE [AspNetUserClaims] ( + [id] int NOT NULL IDENTITY, + [user_id] uniqueidentifier NOT NULL, + [claim_type] nvarchar(max) NULL, + [claim_value] nvarchar(max) NULL, + CONSTRAINT [pk_asp_net_user_claims] PRIMARY KEY ([id]), + CONSTRAINT [fk_asp_net_user_claims_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE +); + +CREATE TABLE [AspNetUserLogins] ( + [login_provider] nvarchar(450) NOT NULL, + [provider_key] nvarchar(450) NOT NULL, + [provider_display_name] nvarchar(max) NULL, + [user_id] uniqueidentifier NOT NULL, + CONSTRAINT [pk_asp_net_user_logins] PRIMARY KEY ([login_provider], [provider_key]), + CONSTRAINT [fk_asp_net_user_logins_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE +); + +CREATE TABLE [AspNetUserRoles] ( + [user_id] uniqueidentifier NOT NULL, + [role_id] uniqueidentifier NOT NULL, + CONSTRAINT [pk_asp_net_user_roles] PRIMARY KEY ([user_id], [role_id]), + CONSTRAINT [fk_asp_net_user_roles_asp_net_roles_role_id] FOREIGN KEY ([role_id]) REFERENCES [AspNetRoles] ([id]) ON DELETE CASCADE, + CONSTRAINT [fk_asp_net_user_roles_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE +); + +CREATE TABLE [AspNetUserTokens] ( + [user_id] uniqueidentifier NOT NULL, + [login_provider] nvarchar(450) NOT NULL, + [name] nvarchar(450) NOT NULL, + [value] nvarchar(max) NULL, + CONSTRAINT [pk_asp_net_user_tokens] PRIMARY KEY ([user_id], [login_provider], [name]), + CONSTRAINT [fk_asp_net_user_tokens_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE +); + +CREATE INDEX [ix_asp_net_role_claims_role_id] ON [AspNetRoleClaims] ([role_id]); + +CREATE UNIQUE INDEX [RoleNameIndex] ON [AspNetRoles] ([normalized_name]) WHERE [normalized_name] IS NOT NULL; + +CREATE INDEX [ix_asp_net_user_claims_user_id] ON [AspNetUserClaims] ([user_id]); + +CREATE INDEX [ix_asp_net_user_logins_user_id] ON [AspNetUserLogins] ([user_id]); + +CREATE INDEX [ix_asp_net_user_roles_role_id] ON [AspNetUserRoles] ([role_id]); + +CREATE INDEX [EmailIndex] ON [AspNetUsers] ([normalized_email]); + +CREATE INDEX [ix_users_country_id] ON [AspNetUsers] ([country_id]); + +CREATE UNIQUE INDEX [UserNameIndex] ON [AspNetUsers] ([normalized_user_name]) WHERE [normalized_user_name] IS NOT NULL; + +CREATE INDEX [ix_asset_file_scan_status] ON [asset_files] ([virus_scan_status]); + +CREATE INDEX [ix_city_result_scenario_at] ON [city_scenario_results] ([scenario_id], [computed_at]); + +CREATE INDEX [ix_city_scenario_user_modified] ON [city_scenarios] ([user_id], [last_modified_on]); + +CREATE INDEX [ix_city_tech_is_active] ON [city_technologies] ([is_active]); + +CREATE INDEX [ix_country_iso_alpha2] ON [countries] ([iso_alpha2]); + +CREATE UNIQUE INDEX [ux_country_iso_alpha3_active] ON [countries] ([iso_alpha3]) WHERE [is_deleted] = 0; + +CREATE INDEX [ix_kapsarc_snapshot_country_taken] ON [country_kapsarc_snapshots] ([country_id], [snapshot_taken_on]); + +CREATE UNIQUE INDEX [ux_country_profile_country_id] ON [country_profiles] ([country_id]); + +CREATE INDEX [ix_country_request_country_status] ON [country_resource_requests] ([country_id], [status]); + +CREATE INDEX [ix_event_starts_on] ON [events] ([starts_on]); + +CREATE UNIQUE INDEX [ux_event_ical_uid] ON [events] ([i_cal_uid]); + +CREATE UNIQUE INDEX [ux_expert_profile_active_user] ON [expert_profiles] ([user_id]) WHERE [is_deleted] = 0; + +CREATE INDEX [ix_expert_request_requested_by] ON [expert_registration_requests] ([requested_by_id]); + +CREATE INDEX [ix_expert_request_status] ON [expert_registration_requests] ([status]); + +CREATE INDEX [ix_homepage_section_active_order] ON [homepage_sections] ([is_active], [order_index]); + +CREATE UNIQUE INDEX [ux_km_assoc_node_type_id] ON [knowledge_map_associations] ([node_id], [associated_type], [associated_id]); + +CREATE INDEX [ix_km_edge_from_node] ON [knowledge_map_edges] ([from_node_id]); + +CREATE INDEX [ix_km_edge_to_node] ON [knowledge_map_edges] ([to_node_id]); + +CREATE UNIQUE INDEX [ux_km_edge_map_from_to_relation] ON [knowledge_map_edges] ([map_id], [from_node_id], [to_node_id], [relationship_type]); + +CREATE INDEX [ix_km_node_map_order] ON [knowledge_map_nodes] ([map_id], [order_index]); + +CREATE UNIQUE INDEX [ux_knowledge_map_slug_active] ON [knowledge_maps] ([slug]) WHERE [is_deleted] = 0; + +CREATE INDEX [ix_news_published_on] ON [news] ([published_on]); + +CREATE UNIQUE INDEX [ux_news_slug_active] ON [news] ([slug]) WHERE [is_deleted] = 0; + +CREATE INDEX [ix_newsletter_token] ON [newsletter_subscriptions] ([confirmation_token]); + +CREATE UNIQUE INDEX [ux_newsletter_email] ON [newsletter_subscriptions] ([email]); + +CREATE UNIQUE INDEX [ux_notification_template_code] ON [notification_templates] ([code]); + +CREATE UNIQUE INDEX [ux_page_type_slug_active] ON [pages] ([page_type], [slug]) WHERE [is_deleted] = 0; + +CREATE UNIQUE INDEX [ux_post_follow_post_user] ON [post_follows] ([post_id], [user_id]); + +CREATE UNIQUE INDEX [ux_post_rating_post_user] ON [post_ratings] ([post_id], [user_id]); + +CREATE INDEX [ix_post_reply_parent_id] ON [post_replies] ([parent_reply_id]); + +CREATE INDEX [ix_post_reply_post_id] ON [post_replies] ([post_id]); + +CREATE INDEX [ix_post_author_created] ON [posts] ([author_id], [created_on]); + +CREATE INDEX [ix_post_topic_id] ON [posts] ([topic_id]); + +CREATE INDEX [ix_resource_category_parent_id] ON [resource_categories] ([parent_id]); + +CREATE UNIQUE INDEX [ux_resource_category_slug] ON [resource_categories] ([slug]); + +CREATE INDEX [ix_resource_asset_file_id] ON [resources] ([asset_file_id]); + +CREATE INDEX [ix_resource_category_published] ON [resources] ([category_id], [published_on]); + +CREATE INDEX [ix_resource_country_id] ON [resources] ([country_id]); + +CREATE INDEX [ix_search_query_log_submitted_on] ON [search_query_logs] ([submitted_on]); + +CREATE INDEX [ix_service_rating_submitted_on] ON [service_ratings] ([submitted_on]); + +CREATE INDEX [ix_state_rep_country_id] ON [state_representative_assignments] ([country_id]); + +CREATE INDEX [ix_state_rep_user_id] ON [state_representative_assignments] ([user_id]); + +CREATE UNIQUE INDEX [ux_state_rep_active_user_country] ON [state_representative_assignments] ([user_id], [country_id]) WHERE [is_deleted] = 0; + +CREATE UNIQUE INDEX [ux_topic_follow_topic_user] ON [topic_follows] ([topic_id], [user_id]); + +CREATE UNIQUE INDEX [ux_topic_slug_active] ON [topics] ([slug]) WHERE [is_deleted] = 0; + +CREATE UNIQUE INDEX [ux_user_follow_follower_followed] ON [user_follows] ([follower_id], [followed_id]); + +CREATE INDEX [ix_user_notification_user_status] ON [user_notifications] ([user_id], [status]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260427192344_DataDomainInitial', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +ALTER TABLE [AspNetUsers] ADD [entra_id_object_id] uniqueidentifier NULL; + +CREATE UNIQUE INDEX [ix_asp_net_users_entra_id_object_id] ON [AspNetUsers] ([entra_id_object_id]) WHERE [entra_id_object_id] IS NOT NULL; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260504182534_AddEntraIdObjectIdToUser', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +ALTER TABLE [AspNetUsers] ADD [first_name] nvarchar(50) NOT NULL DEFAULT N''; + +ALTER TABLE [AspNetUsers] ADD [job_title] nvarchar(50) NOT NULL DEFAULT N''; + +ALTER TABLE [AspNetUsers] ADD [last_name] nvarchar(50) NOT NULL DEFAULT N''; + +ALTER TABLE [AspNetUsers] ADD [organization_name] nvarchar(100) NOT NULL DEFAULT N''; + +CREATE TABLE [refresh_tokens] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [token_hash] nvarchar(128) NOT NULL, + [token_family_id] uniqueidentifier NOT NULL, + [created_at_utc] datetimeoffset NOT NULL, + [expires_at_utc] datetimeoffset NOT NULL, + [revoked_at_utc] datetimeoffset NULL, + [replaced_by_token_hash] nvarchar(128) NULL, + [created_by_ip] nvarchar(64) NULL, + [revoked_by_ip] nvarchar(64) NULL, + [user_agent] nvarchar(512) NULL, + CONSTRAINT [pk_refresh_tokens] PRIMARY KEY ([id]), + CONSTRAINT [fk_refresh_tokens_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE +); + +CREATE INDEX [ix_refresh_tokens_token_family_id] ON [refresh_tokens] ([token_family_id]); + +CREATE INDEX [ix_refresh_tokens_user_id] ON [refresh_tokens] ([user_id]); + +CREATE UNIQUE INDEX [ux_refresh_tokens_token_hash] ON [refresh_tokens] ([token_hash]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260514202038_AddLocalAuthRefreshTokens', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; + + IF EXISTS ( + SELECT 1 FROM sys.columns c + JOIN sys.tables t ON c.object_id = t.object_id + WHERE t.name = 'country_profiles' AND c.name = 'last_updated_on' + ) + BEGIN + EXEC sp_rename N'[country_profiles].[last_updated_on]', N'created_on', 'COLUMN'; + END + + IF EXISTS ( + SELECT 1 FROM sys.columns c + JOIN sys.tables t ON c.object_id = t.object_id + WHERE t.name = 'country_profiles' AND c.name = 'last_updated_by_id' + ) + BEGIN + EXEC sp_rename N'[country_profiles].[last_updated_by_id]', N'created_by_id', 'COLUMN'; + END + + +ALTER TABLE [topics] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [topics] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [topics] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [topics] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [state_representative_assignments] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [state_representative_assignments] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [state_representative_assignments] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [state_representative_assignments] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [resources] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [resources] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [resources] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [resources] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [posts] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [posts] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [posts] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [post_replies] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [post_replies] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [post_replies] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [pages] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [pages] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [pages] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [pages] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [news] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [news] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [news] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [news] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [knowledge_maps] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [knowledge_maps] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [knowledge_maps] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [knowledge_maps] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [homepage_sections] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [homepage_sections] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [homepage_sections] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [homepage_sections] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [expert_registration_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [expert_registration_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [expert_registration_requests] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [expert_registration_requests] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [expert_profiles] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [expert_profiles] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [expert_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [expert_profiles] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [events] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [events] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [events] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [events] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [country_resource_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [country_resource_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [country_resource_requests] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [country_resource_requests] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [country_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [country_profiles] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [countries] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [countries] ADD [last_modified_on] datetimeoffset NULL; + +DECLARE @var nvarchar(max); +SELECT @var = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[city_scenarios]') AND [c].[name] = N'last_modified_on'); +IF @var IS NOT NULL EXEC(N'ALTER TABLE [city_scenarios] DROP CONSTRAINT ' + @var + ';'); +ALTER TABLE [city_scenarios] ALTER COLUMN [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [city_scenarios] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [city_scenarios] ADD [last_modified_by_id] uniqueidentifier NULL; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260515121258_StandardizeCountryProfileAudit', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +EXEC sp_rename N'[country_profiles].[last_updated_on]', N'created_on', 'COLUMN'; + +EXEC sp_rename N'[country_profiles].[last_updated_by_id]', N'created_by_id', 'COLUMN'; + +ALTER TABLE [topics] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [topics] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [topics] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [topics] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [state_representative_assignments] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [state_representative_assignments] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [state_representative_assignments] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [state_representative_assignments] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [resources] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [resources] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [resources] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [resources] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [posts] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [posts] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [posts] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [post_replies] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [post_replies] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [post_replies] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [pages] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [pages] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [pages] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [pages] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [newsletter_subscriptions] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [newsletter_subscriptions] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [newsletter_subscriptions] ADD [deleted_by_id] uniqueidentifier NULL; + +ALTER TABLE [newsletter_subscriptions] ADD [deleted_on] datetimeoffset NULL; + +ALTER TABLE [newsletter_subscriptions] ADD [is_deleted] bit NOT NULL DEFAULT CAST(0 AS bit); + +ALTER TABLE [newsletter_subscriptions] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [newsletter_subscriptions] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [news] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [news] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [news] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [news] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [knowledge_maps] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [knowledge_maps] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [knowledge_maps] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [knowledge_maps] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [homepage_sections] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [homepage_sections] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [homepage_sections] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [homepage_sections] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [expert_registration_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [expert_registration_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [expert_registration_requests] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [expert_registration_requests] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [expert_profiles] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [expert_profiles] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [expert_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [expert_profiles] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [events] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [events] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [events] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [events] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [country_resource_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [country_resource_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [country_resource_requests] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [country_resource_requests] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [country_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [country_profiles] ADD [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [countries] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [countries] ADD [last_modified_on] datetimeoffset NULL; + +DECLARE @var1 nvarchar(max); +SELECT @var1 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[city_scenarios]') AND [c].[name] = N'last_modified_on'); +IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [city_scenarios] DROP CONSTRAINT ' + @var1 + ';'); +ALTER TABLE [city_scenarios] ALTER COLUMN [last_modified_on] datetimeoffset NULL; + +ALTER TABLE [city_scenarios] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [city_scenarios] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [AspNetUsers] ADD [status] int NOT NULL DEFAULT 0; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260520101638_AddUserStatus', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +ALTER TABLE [AspNetUsers] ADD [deleted_by_id] uniqueidentifier NULL; + +ALTER TABLE [AspNetUsers] ADD [deleted_on] datetimeoffset NULL; + +ALTER TABLE [AspNetUsers] ADD [is_deleted] bit NOT NULL DEFAULT CAST(0 AS bit); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260520111756_AddUserSoftDelete', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +CREATE TABLE [about_settings] ( + [id] uniqueidentifier NOT NULL, + [description_ar] nvarchar(1000) NOT NULL, + [description_en] nvarchar(1000) NOT NULL, + [how_to_use_video_url] nvarchar(max) NULL, + [row_version] rowversion NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_about_settings] PRIMARY KEY ([id]) +); + +CREATE TABLE [glossary_entries] ( + [id] uniqueidentifier NOT NULL, + [about_settings_id] uniqueidentifier NOT NULL, + [term_ar] nvarchar(100) NOT NULL, + [term_en] nvarchar(100) NOT NULL, + [definition_ar] nvarchar(1000) NOT NULL, + [definition_en] nvarchar(1000) NOT NULL, + [order_index] int NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_glossary_entries] PRIMARY KEY ([id]) +); + +CREATE TABLE [homepage_countries] ( + [id] uniqueidentifier NOT NULL, + [homepage_settings_id] uniqueidentifier NOT NULL, + [country_id] uniqueidentifier NOT NULL, + [order_index] int NOT NULL, + CONSTRAINT [pk_homepage_countries] PRIMARY KEY ([id]) +); + +CREATE TABLE [homepage_settings] ( + [id] uniqueidentifier NOT NULL, + [video_url] nvarchar(max) NULL, + [objective_ar] nvarchar(1000) NOT NULL, + [objective_en] nvarchar(1000) NOT NULL, + [cce_concepts_ar] nvarchar(max) NOT NULL, + [cce_concepts_en] nvarchar(max) NOT NULL, + [row_version] rowversion NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_homepage_settings] PRIMARY KEY ([id]) +); + +CREATE TABLE [knowledge_partners] ( + [id] uniqueidentifier NOT NULL, + [about_settings_id] uniqueidentifier NOT NULL, + [name_ar] nvarchar(200) NOT NULL, + [name_en] nvarchar(200) NOT NULL, + [logo_url] nvarchar(max) NULL, + [website_url] nvarchar(max) NULL, + [description_ar] nvarchar(1000) NULL, + [description_en] nvarchar(1000) NULL, + [order_index] int NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_knowledge_partners] PRIMARY KEY ([id]) +); + +CREATE TABLE [policies_settings] ( + [id] uniqueidentifier NOT NULL, + [row_version] rowversion NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_policies_settings] PRIMARY KEY ([id]) +); + +CREATE TABLE [policy_sections] ( + [id] uniqueidentifier NOT NULL, + [policies_settings_id] uniqueidentifier NOT NULL, + [type] int NOT NULL, + [title_ar] nvarchar(500) NOT NULL, + [title_en] nvarchar(500) NOT NULL, + [content_ar] nvarchar(max) NOT NULL, + [content_en] nvarchar(max) NOT NULL, + [order_index] int NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_policy_sections] PRIMARY KEY ([id]) +); + +CREATE UNIQUE INDEX [ix_homepage_country_settings_country] ON [homepage_countries] ([homepage_settings_id], [country_id]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260521094531_AddPlatformSettings', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +CREATE TABLE [media_files] ( + [id] uniqueidentifier NOT NULL, + [storage_key] nvarchar(500) NOT NULL, + [url] nvarchar(2048) NOT NULL, + [original_file_name] nvarchar(255) NOT NULL, + [mime_type] nvarchar(100) NOT NULL, + [size_bytes] bigint NOT NULL, + [title_ar] nvarchar(200) NULL, + [title_en] nvarchar(200) NULL, + [description_ar] nvarchar(1000) NULL, + [description_en] nvarchar(1000) NULL, + [alt_text_ar] nvarchar(500) NULL, + [alt_text_en] nvarchar(500) NULL, + [uploaded_by_id] uniqueidentifier NOT NULL, + [uploaded_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_media_files] PRIMARY KEY ([id]) +); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260521111720_AddMediaService', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +DECLARE @var2 nvarchar(max); +SELECT @var2 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'deleted_by_id'); +IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var2 + ';'); +ALTER TABLE [policy_sections] DROP COLUMN [deleted_by_id]; + +DECLARE @var3 nvarchar(max); +SELECT @var3 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'deleted_on'); +IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var3 + ';'); +ALTER TABLE [policy_sections] DROP COLUMN [deleted_on]; + +DECLARE @var4 nvarchar(max); +SELECT @var4 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'is_deleted'); +IF @var4 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var4 + ';'); +ALTER TABLE [policy_sections] DROP COLUMN [is_deleted]; + +DECLARE @var5 nvarchar(max); +SELECT @var5 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'deleted_by_id'); +IF @var5 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var5 + ';'); +ALTER TABLE [knowledge_partners] DROP COLUMN [deleted_by_id]; + +DECLARE @var6 nvarchar(max); +SELECT @var6 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'deleted_on'); +IF @var6 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var6 + ';'); +ALTER TABLE [knowledge_partners] DROP COLUMN [deleted_on]; + +DECLARE @var7 nvarchar(max); +SELECT @var7 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'is_deleted'); +IF @var7 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var7 + ';'); +ALTER TABLE [knowledge_partners] DROP COLUMN [is_deleted]; + +DECLARE @var8 nvarchar(max); +SELECT @var8 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'deleted_by_id'); +IF @var8 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var8 + ';'); +ALTER TABLE [glossary_entries] DROP COLUMN [deleted_by_id]; + +DECLARE @var9 nvarchar(max); +SELECT @var9 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'deleted_on'); +IF @var9 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var9 + ';'); +ALTER TABLE [glossary_entries] DROP COLUMN [deleted_on]; + +DECLARE @var10 nvarchar(max); +SELECT @var10 = QUOTENAME([d].[name]) +FROM [sys].[default_constraints] [d] +INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] +WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'is_deleted'); +IF @var10 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var10 + ';'); +ALTER TABLE [glossary_entries] DROP COLUMN [is_deleted]; + +ALTER TABLE [homepage_countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; + +ALTER TABLE [homepage_countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; + +ALTER TABLE [homepage_countries] ADD [last_modified_by_id] uniqueidentifier NULL; + +ALTER TABLE [homepage_countries] ADD [last_modified_on] datetimeoffset NULL; + +CREATE INDEX [ix_policy_sections_policies_settings_id] ON [policy_sections] ([policies_settings_id]); + +CREATE INDEX [ix_knowledge_partners_about_settings_id] ON [knowledge_partners] ([about_settings_id]); + +CREATE INDEX [ix_glossary_entries_about_settings_id] ON [glossary_entries] ([about_settings_id]); + +ALTER TABLE [glossary_entries] ADD CONSTRAINT [fk_glossary_entries_about_settings_about_settings_id] FOREIGN KEY ([about_settings_id]) REFERENCES [about_settings] ([id]) ON DELETE CASCADE; + +ALTER TABLE [homepage_countries] ADD CONSTRAINT [fk_homepage_countries_homepage_settings_homepage_settings_id] FOREIGN KEY ([homepage_settings_id]) REFERENCES [homepage_settings] ([id]) ON DELETE CASCADE; + +ALTER TABLE [knowledge_partners] ADD CONSTRAINT [fk_knowledge_partners_about_settings_about_settings_id] FOREIGN KEY ([about_settings_id]) REFERENCES [about_settings] ([id]) ON DELETE CASCADE; + +ALTER TABLE [policy_sections] ADD CONSTRAINT [fk_policy_sections_policies_settings_policies_settings_id] FOREIGN KEY ([policies_settings_id]) REFERENCES [policies_settings] ([id]) ON DELETE CASCADE; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260522211302_RefactorPlatformSettings', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +DROP INDEX [ux_notification_template_code] ON [notification_templates]; + +CREATE TABLE [notification_logs] ( + [id] uniqueidentifier NOT NULL, + [recipient_user_id] uniqueidentifier NULL, + [template_code] nvarchar(64) NOT NULL, + [template_id] uniqueidentifier NULL, + [channel] int NOT NULL, + [status] int NOT NULL, + [provider_message_id] nvarchar(256) NULL, + [error] nvarchar(max) NULL, + [attempt_count] int NOT NULL, + [created_on] datetimeoffset NOT NULL, + [sent_on] datetimeoffset NULL, + [failed_on] datetimeoffset NULL, + [correlation_id] nvarchar(64) NULL, + [payload_json] nvarchar(max) NULL, + CONSTRAINT [pk_notification_logs] PRIMARY KEY ([id]) +); + +CREATE TABLE [user_notification_settings] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NOT NULL, + [channel] int NOT NULL, + [event_code] nvarchar(64) NULL, + [is_enabled] bit NOT NULL, + [updated_on] datetimeoffset NOT NULL, + CONSTRAINT [pk_user_notification_settings] PRIMARY KEY ([id]) +); + +CREATE UNIQUE INDEX [ux_notification_template_code_channel] ON [notification_templates] ([code], [channel]); + +CREATE INDEX [ix_notification_log_correlation_id] ON [notification_logs] ([correlation_id]); + +CREATE INDEX [ix_notification_log_recipient_status_created] ON [notification_logs] ([recipient_user_id], [status], [created_on]); + +CREATE INDEX [ix_notification_log_template_channel] ON [notification_logs] ([template_code], [channel]); + +CREATE UNIQUE INDEX [ux_user_notification_settings_user_channel_event] ON [user_notification_settings] ([user_id], [channel], [event_code]) WHERE [event_code] IS NOT NULL; + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260523111750_AddNotificationGateway', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +CREATE TABLE [otp_verifications] ( + [id] uniqueidentifier NOT NULL, + [contact] nvarchar(256) NOT NULL, + [type_id] int NOT NULL, + [code_hash] nvarchar(512) NOT NULL, + [expires_at] datetimeoffset NOT NULL, + [created_at] datetimeoffset NOT NULL, + [last_sent_at] datetimeoffset NULL, + [attempt_count] int NOT NULL, + [is_verified] bit NOT NULL, + [is_invalidated] bit NOT NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_otp_verifications] PRIMARY KEY ([id]) +); + +CREATE TABLE [user_verifications] ( + [id] uniqueidentifier NOT NULL, + [user_id] uniqueidentifier NULL, + [contact] nvarchar(256) NOT NULL, + [type_id] int NOT NULL, + [is_verified] bit NOT NULL, + [verified_at] datetimeoffset NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + [is_deleted] bit NOT NULL, + [deleted_on] datetimeoffset NULL, + [deleted_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_user_verifications] PRIMARY KEY ([id]), + CONSTRAINT [fk_user_verifications_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) +); + +CREATE INDEX [ix_otp_verifications_contact_type_id] ON [otp_verifications] ([contact], [type_id]); + +CREATE UNIQUE INDEX [ix_user_verifications_contact_type_id] ON [user_verifications] ([contact], [type_id]); + +CREATE INDEX [ix_user_verifications_user_id] ON [user_verifications] ([user_id]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260523180351_AddOtpVerification', N'10.0.1'); + +COMMIT; +GO + +BEGIN TRANSACTION; +CREATE TABLE [service_evaluations] ( + [id] uniqueidentifier NOT NULL, + [overall_satisfaction] int NOT NULL, + [ease_of_use] int NOT NULL, + [content_suitability] int NOT NULL, + [feedback] nvarchar(500) NOT NULL, + [user_id] uniqueidentifier NULL, + [created_on] datetimeoffset NOT NULL, + [created_by_id] uniqueidentifier NOT NULL, + [last_modified_on] datetimeoffset NULL, + [last_modified_by_id] uniqueidentifier NULL, + CONSTRAINT [pk_service_evaluations] PRIMARY KEY ([id]) +); + +CREATE INDEX [ix_service_evaluation_created_on] ON [service_evaluations] ([created_on]); + +INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) +VALUES (N'20260525092623_AddServiceEvaluation', N'10.0.1'); + +COMMIT; +GO + diff --git a/backend/src/CCE.Api.Common/Localization/Resources.yaml b/backend/src/CCE.Api.Common/Localization/Resources.yaml index 89c98f96..853039f8 100644 --- a/backend/src/CCE.Api.Common/Localization/Resources.yaml +++ b/backend/src/CCE.Api.Common/Localization/Resources.yaml @@ -456,3 +456,11 @@ NOTIFICATION_TEMPLATE_CREATED: NOTIFICATION_TEMPLATE_UPDATED: ar: "تم تحديث قالب الإشعار بنجاح" en: "Notification template updated successfully" + +EVALUATION_NOT_FOUND: + ar: "التقييم غير موجود" + en: "Evaluation not found" + +EVALUATION_SUBMITTED: + ar: "تم تقديم التقييم بنجاح" + en: "Evaluation submitted successfully" diff --git a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs new file mode 100644 index 00000000..a25620fe --- /dev/null +++ b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs @@ -0,0 +1,42 @@ +using CCE.Api.Common.Extensions; +using CCE.Application.Evaluation.Commands.SubmitEvaluation; +using CCE.Domain.Evaluation; +using MediatR; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace CCE.Api.External.Endpoints; + +public static class EvaluationEndpoints +{ + public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/api/evaluations").WithTags("Evaluations"); + + // POST /api/evaluations — public submit (visitors & authenticated users) + group.MapPost("", async ( + SubmitEvaluationRequest body, + IMediator mediator, + CancellationToken ct) => + { + var cmd = new SubmitEvaluationCommand( + body.OverallSatisfaction, + body.EaseOfUse, + body.ContentSuitability, + body.Feedback); + var result = await mediator.Send(cmd, ct).ConfigureAwait(false); + return result.ToHttpResult(StatusCodes.Status201Created); + }) + .AllowAnonymous() + .WithName("SubmitEvaluation"); + + return app; + } +} + +public sealed record SubmitEvaluationRequest( + EvaluationRating OverallSatisfaction, + EvaluationRating EaseOfUse, + EvaluationRating ContentSuitability, + string Feedback); diff --git a/backend/src/CCE.Api.External/Program.cs b/backend/src/CCE.Api.External/Program.cs index 175570ef..4b5b703b 100644 --- a/backend/src/CCE.Api.External/Program.cs +++ b/backend/src/CCE.Api.External/Program.cs @@ -113,6 +113,7 @@ app.MapAssistantEndpoints(); app.MapKapsarcEndpoints(); app.MapSurveysEndpoints(); +app.MapEvaluationEndpoints(); app.MapHomepageSettingsPublicEndpoints(); app.MapAboutSettingsPublicEndpoints(); app.MapPoliciesSettingsPublicEndpoints(); diff --git a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs new file mode 100644 index 00000000..ecaa86bd --- /dev/null +++ b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs @@ -0,0 +1,42 @@ +using CCE.Application.Evaluation.Queries.GetAllEvaluations; +using CCE.Application.Evaluation.Queries.GetEvaluationById; +using CCE.Domain; +using MediatR; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace CCE.Api.Internal.Endpoints; + +public static class EvaluationEndpoints +{ + public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBuilder app) + { + var group = app.MapGroup("/api/admin/evaluations").WithTags("Evaluations"); + + // GET /api/admin/evaluations — list all (admin only) + group.MapGet("", async ( + IMediator mediator, + CancellationToken ct) => + { + var result = await mediator.Send(new GetAllEvaluationsQuery(), ct).ConfigureAwait(false); + return Results.Ok(result); + }) + .RequireAuthorization(Permissions.Survey_ReadAll) + .WithName("GetAllEvaluations"); + + // GET /api/admin/evaluations/{id} — get by id (admin only) + group.MapGet("{id:guid}", async ( + System.Guid id, + IMediator mediator, + CancellationToken ct) => + { + var result = await mediator.Send(new GetEvaluationByIdQuery(id), ct).ConfigureAwait(false); + return result is null ? Results.NotFound() : Results.Ok(result); + }) + .RequireAuthorization(Permissions.Survey_ReadAll) + .WithName("GetEvaluationById"); + + return app; + } +} diff --git a/backend/src/CCE.Api.Internal/Program.cs b/backend/src/CCE.Api.Internal/Program.cs index 0f4a848e..cc70452d 100644 --- a/backend/src/CCE.Api.Internal/Program.cs +++ b/backend/src/CCE.Api.Internal/Program.cs @@ -86,6 +86,7 @@ app.MapAboutSettingsEndpoints(); app.MapPoliciesSettingsEndpoints(); app.MapMediaEndpoints(); +app.MapEvaluationEndpoints(); // Sub-11d follow-up — dev sign-in shim. Mounts /dev/sign-in, // /dev/sign-out, /dev/whoami when Auth:DevMode=true. Production diff --git a/backend/src/CCE.Application/Common/Errors.cs b/backend/src/CCE.Application/Common/Errors.cs index 7ed5530b..7b934c5f 100644 --- a/backend/src/CCE.Application/Common/Errors.cs +++ b/backend/src/CCE.Application/Common/Errors.cs @@ -55,6 +55,9 @@ public Error RegistrationFailed(IDictionary? details = null) public Error PostNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.POST_NOT_FOUND}"); public Error ReplyNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.REPLY_NOT_FOUND}"); + // ─── Convenience: Evaluation domain ─── + public Error EvaluationNotFound() => NotFound($"EVALUATION_{ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND}"); + // ─── Convenience: Country domain ─── public Error CountryNotFound() => NotFound($"COUNTRY_{ApplicationErrors.Country.COUNTRY_NOT_FOUND}"); diff --git a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs index 9e288bd6..db9f9473 100644 --- a/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs +++ b/backend/src/CCE.Application/Common/Interfaces/ICceDbContext.cs @@ -2,6 +2,7 @@ using CCE.Domain.Audit; using CCE.Domain.Community; using CCE.Domain.Content; +using CCE.Domain.Evaluation; using CCE.Domain.Identity; using CCE.Domain.InteractiveCity; using CCE.Domain.KnowledgeMaps; @@ -75,6 +76,9 @@ public interface ICceDbContext IQueryable OtpVerifications { get; } IQueryable UserVerifications { get; } + // ─── Evaluation ─── + IQueryable ServiceEvaluations { get; } + // ─── Media ─── IQueryable MediaFiles { get; } diff --git a/backend/src/CCE.Application/Errors/ApplicationErrors.cs b/backend/src/CCE.Application/Errors/ApplicationErrors.cs index 6fa696d5..27c95fdb 100644 --- a/backend/src/CCE.Application/Errors/ApplicationErrors.cs +++ b/backend/src/CCE.Application/Errors/ApplicationErrors.cs @@ -104,6 +104,12 @@ public static class InteractiveCity public const string TECHNOLOGY_NOT_FOUND = "TECHNOLOGY_NOT_FOUND"; } + public static class Evaluation + { + public const string EVALUATION_NOT_FOUND = "EVALUATION_NOT_FOUND"; + public const string EVALUATION_SUBMITTED = "EVALUATION_SUBMITTED"; + } + public static class Validation { public const string REQUIRED_FIELD = "REQUIRED_FIELD"; diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs new file mode 100644 index 00000000..a3eae52d --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommand.cs @@ -0,0 +1,11 @@ +using CCE.Application.Common; +using CCE.Domain.Evaluation; +using MediatR; + +namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; + +public sealed record SubmitEvaluationCommand( + EvaluationRating OverallSatisfaction, + EvaluationRating EaseOfUse, + EvaluationRating ContentSuitability, + string Feedback) : IRequest>; diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs new file mode 100644 index 00000000..e517f7b2 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs @@ -0,0 +1,49 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Errors; +using CCE.Application.Messages; +using CCE.Domain.Common; +using DomainEvaluation = CCE.Domain.Evaluation.ServiceEvaluation; +using MediatR; + +namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; + +public sealed class SubmitEvaluationCommandHandler + : IRequestHandler> +{ + private readonly IEvaluationRepository _repository; + private readonly ICurrentUserAccessor _currentUser; + private readonly ISystemClock _clock; + private readonly MessageFactory _messageFactory; + + public SubmitEvaluationCommandHandler( + IEvaluationRepository repository, + ICurrentUserAccessor currentUser, + ISystemClock clock, + MessageFactory messageFactory) + { + _repository = repository; + _currentUser = currentUser; + _clock = clock; + _messageFactory = messageFactory; + } + + public async Task> Handle( + SubmitEvaluationCommand request, + CancellationToken cancellationToken) + { + var userId = _currentUser.GetUserId(); + + var evaluation = DomainEvaluation.Submit( + request.OverallSatisfaction, + request.EaseOfUse, + request.ContentSuitability, + request.Feedback, + userId, + _clock); + + await _repository.AddAsync(evaluation, cancellationToken).ConfigureAwait(false); + + return _messageFactory.Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); + } +} diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs new file mode 100644 index 00000000..8da2de7a --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs @@ -0,0 +1,15 @@ +using CCE.Domain.Evaluation; +using FluentValidation; + +namespace CCE.Application.Evaluation.Commands.SubmitEvaluation; + +public sealed class SubmitEvaluationCommandValidator : AbstractValidator +{ + public SubmitEvaluationCommandValidator() + { + RuleFor(x => x.OverallSatisfaction).IsInEnum().NotEqual(EvaluationRating.None); + RuleFor(x => x.EaseOfUse).IsInEnum().NotEqual(EvaluationRating.None); + RuleFor(x => x.ContentSuitability).IsInEnum().NotEqual(EvaluationRating.None); + RuleFor(x => x.Feedback).NotEmpty().MaximumLength(500); + } +} diff --git a/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs new file mode 100644 index 00000000..962551ed --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs @@ -0,0 +1,13 @@ +using CCE.Domain.Evaluation; + +namespace CCE.Application.Evaluation.DTOs; + +public sealed record ServiceEvaluationDto( + System.Guid Id, + EvaluationRating OverallSatisfaction, + EvaluationRating EaseOfUse, + EvaluationRating ContentSuitability, + string Feedback, + System.Guid? UserId, + System.DateTimeOffset CreatedOn, + System.Guid CreatedById); diff --git a/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs new file mode 100644 index 00000000..e382ffae --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs @@ -0,0 +1,10 @@ +using CCE.Domain.Evaluation; + +namespace CCE.Application.Evaluation; + +public interface IEvaluationRepository +{ + Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct); + Task> GetAllAsync(CancellationToken ct); + Task GetByIdAsync(System.Guid id, CancellationToken ct); +} diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs new file mode 100644 index 00000000..2249a409 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs @@ -0,0 +1,6 @@ +using CCE.Application.Evaluation.DTOs; +using MediatR; + +namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; + +public sealed record GetAllEvaluationsQuery : IRequest>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs new file mode 100644 index 00000000..fb41b435 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs @@ -0,0 +1,32 @@ +using CCE.Application.Evaluation.DTOs; +using MediatR; + +namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; + +public sealed class GetAllEvaluationsQueryHandler + : IRequestHandler> +{ + private readonly IEvaluationRepository _repository; + + public GetAllEvaluationsQueryHandler(IEvaluationRepository repository) + { + _repository = repository; + } + + public async Task> Handle( + GetAllEvaluationsQuery request, + CancellationToken cancellationToken) + { + var evaluations = await _repository.GetAllAsync(cancellationToken).ConfigureAwait(false); + + return evaluations.Select(e => new ServiceEvaluationDto( + e.Id, + e.OverallSatisfaction, + e.EaseOfUse, + e.ContentSuitability, + e.Feedback, + e.UserId, + e.CreatedOn, + e.CreatedById)).ToList(); + } +} diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs new file mode 100644 index 00000000..6bba2c74 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs @@ -0,0 +1,6 @@ +using CCE.Application.Evaluation.DTOs; +using MediatR; + +namespace CCE.Application.Evaluation.Queries.GetEvaluationById; + +public sealed record GetEvaluationByIdQuery(System.Guid Id) : IRequest; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs new file mode 100644 index 00000000..f6874550 --- /dev/null +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs @@ -0,0 +1,34 @@ +using CCE.Application.Evaluation.DTOs; +using MediatR; + +namespace CCE.Application.Evaluation.Queries.GetEvaluationById; + +public sealed class GetEvaluationByIdQueryHandler + : IRequestHandler +{ + private readonly IEvaluationRepository _repository; + + public GetEvaluationByIdQueryHandler(IEvaluationRepository repository) + { + _repository = repository; + } + + public async Task Handle( + GetEvaluationByIdQuery request, + CancellationToken cancellationToken) + { + var evaluation = await _repository.GetByIdAsync(request.Id, cancellationToken).ConfigureAwait(false); + + if (evaluation is null) return null; + + return new ServiceEvaluationDto( + evaluation.Id, + evaluation.OverallSatisfaction, + evaluation.EaseOfUse, + evaluation.ContentSuitability, + evaluation.Feedback, + evaluation.UserId, + evaluation.CreatedOn, + evaluation.CreatedById); + } +} diff --git a/backend/src/CCE.Application/Messages/MessageFactory.cs b/backend/src/CCE.Application/Messages/MessageFactory.cs index 5d07dea3..8f13107b 100644 --- a/backend/src/CCE.Application/Messages/MessageFactory.cs +++ b/backend/src/CCE.Application/Messages/MessageFactory.cs @@ -1,4 +1,5 @@ using CCE.Application.Common; +using CCE.Application.Errors; using CCE.Application.Localization; using CCE.Domain.Common; @@ -105,6 +106,10 @@ public FieldError Field(string fieldName, string domainKey) public Response OtpCooldownActive() => BusinessRule("OTP_COOLDOWN_ACTIVE"); public Response OtpInvalidated() => BusinessRule("OTP_INVALIDATED"); + // ─── Convenience shortcuts (Evaluation domain) ─── + public Response EvaluationSubmitted() => Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); + public Response EvaluationNotFound() => NotFound(ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND); + // ─── Convenience shortcuts (Notification domain) ─── public Response NotificationTemplateNotFound() => NotFound("TEMPLATE_NOT_FOUND"); diff --git a/backend/src/CCE.Application/Messages/SystemCode.cs b/backend/src/CCE.Application/Messages/SystemCode.cs index 86f26693..6dae161d 100644 --- a/backend/src/CCE.Application/Messages/SystemCode.cs +++ b/backend/src/CCE.Application/Messages/SystemCode.cs @@ -113,6 +113,9 @@ public static class SystemCode public const string ERR124 = "ERR124"; // OTP cooldown active public const string ERR125 = "ERR125"; // OTP invalidated + // ─── Evaluation Errors ─── + public const string ERR009 = "ERR009"; // Evaluation not found + // ─── General Errors ─── public const string ERR900 = "ERR900"; // Internal server error public const string ERR901 = "ERR901"; // Unauthorized access diff --git a/backend/src/CCE.Application/Messages/SystemCodeMap.cs b/backend/src/CCE.Application/Messages/SystemCodeMap.cs index d136a013..72b27452 100644 --- a/backend/src/CCE.Application/Messages/SystemCodeMap.cs +++ b/backend/src/CCE.Application/Messages/SystemCodeMap.cs @@ -93,6 +93,9 @@ public static class SystemCodeMap ["OTP_COOLDOWN_ACTIVE"] = SystemCode.ERR124, ["OTP_INVALIDATED"] = SystemCode.ERR125, + // ─── Evaluation Errors ─── + ["EVALUATION_NOT_FOUND"] = SystemCode.ERR009, + // ─── General Errors ─── ["INTERNAL_ERROR"] = SystemCode.ERR900, ["UNAUTHORIZED_ACCESS"] = SystemCode.ERR901, @@ -156,6 +159,9 @@ public static class SystemCodeMap ["OTP_SENT"] = SystemCode.CON060, ["OTP_VERIFIED"] = SystemCode.CON061, + // ─── Evaluation Success ─── + ["EVALUATION_SUBMITTED"] = SystemCode.CON008, + // ─── General Success ─── ["ITEMS_LISTED"] = SystemCode.CON100, ["SUCCESS_OPERATION"] = SystemCode.CON900, diff --git a/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs b/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs new file mode 100644 index 00000000..785ccb03 --- /dev/null +++ b/backend/src/CCE.Domain/Evaluation/EvaluationRating.cs @@ -0,0 +1,11 @@ +namespace CCE.Domain.Evaluation; + +public enum EvaluationRating +{ + None = 0, + Excellent = 1, + Satisfied = 2, + Neutral = 3, + Dissatisfied = 4, + Poor = 5 +} diff --git a/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs new file mode 100644 index 00000000..e443cf51 --- /dev/null +++ b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs @@ -0,0 +1,61 @@ +using CCE.Domain.Common; + +namespace CCE.Domain.Evaluation; + +[Audited] +public sealed class ServiceEvaluation : AuditableEntity +{ + private static readonly System.Guid AnonymousVisitorId = new("00000000-0000-0000-0000-000000000001"); + + private ServiceEvaluation( + System.Guid id, + EvaluationRating overallSatisfaction, + EvaluationRating easeOfUse, + EvaluationRating contentSuitability, + string feedback, + System.Guid? userId) : base(id) + { + OverallSatisfaction = overallSatisfaction; + EaseOfUse = easeOfUse; + ContentSuitability = contentSuitability; + Feedback = feedback; + UserId = userId; + } + + public EvaluationRating OverallSatisfaction { get; private set; } + public EvaluationRating EaseOfUse { get; private set; } + public EvaluationRating ContentSuitability { get; private set; } + public string Feedback { get; private set; } + public System.Guid? UserId { get; private set; } + + public static ServiceEvaluation Submit( + EvaluationRating overallSatisfaction, + EvaluationRating easeOfUse, + EvaluationRating contentSuitability, + string feedback, + System.Guid? userId, + ISystemClock clock) + { + if (overallSatisfaction == EvaluationRating.None) + throw new DomainException("OverallSatisfaction is required."); + if (easeOfUse == EvaluationRating.None) + throw new DomainException("EaseOfUse is required."); + if (contentSuitability == EvaluationRating.None) + throw new DomainException("ContentSuitability is required."); + if (string.IsNullOrWhiteSpace(feedback)) + throw new DomainException("Feedback is required."); + + var entity = new ServiceEvaluation( + System.Guid.NewGuid(), + overallSatisfaction, + easeOfUse, + contentSuitability, + feedback.Trim(), + userId); + + entity.CreatedOn = clock.UtcNow; + entity.CreatedById = userId ?? AnonymousVisitorId; + + return entity; + } +} diff --git a/backend/src/CCE.Infrastructure/DependencyInjection.cs b/backend/src/CCE.Infrastructure/DependencyInjection.cs index fb63c3bc..4fc0888d 100644 --- a/backend/src/CCE.Infrastructure/DependencyInjection.cs +++ b/backend/src/CCE.Infrastructure/DependencyInjection.cs @@ -5,6 +5,7 @@ using CCE.Application.Community; using CCE.Application.Content; using CCE.Application.Content.Public; +using CCE.Application.Evaluation; using CCE.Application.Media; using CCE.Application.PlatformSettings; using CCE.Application.Country; @@ -28,6 +29,7 @@ using CCE.Infrastructure.Notifications; using CCE.Infrastructure.Notifications.Messaging; using CCE.Infrastructure.Reports; +using CCE.Infrastructure.Evaluation; using CCE.Infrastructure.Surveys; using CCE.Application.Verification; using CCE.Application.Localization; @@ -221,6 +223,9 @@ public static IServiceCollection AddInfrastructure( // Surveys services.AddScoped(); + // Evaluation + services.AddScoped(); + // Smart assistant — factory routes to stub or Anthropic based on // Assistant:Provider config + ANTHROPIC_API_KEY env-var (Sub-10a). services.AddScoped(); diff --git a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs new file mode 100644 index 00000000..7dcbbf35 --- /dev/null +++ b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs @@ -0,0 +1,37 @@ +using CCE.Application.Evaluation; +using CCE.Domain.Evaluation; +using CCE.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace CCE.Infrastructure.Evaluation; + +public sealed class EvaluationRepository : IEvaluationRepository +{ + private readonly CceDbContext _db; + + public EvaluationRepository(CceDbContext db) + { + _db = db; + } + + public async Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct) + { + _db.ServiceEvaluations.Add(evaluation); + await _db.SaveChangesAsync(ct).ConfigureAwait(false); + } + + public async Task> GetAllAsync(CancellationToken ct) + { + return await _db.ServiceEvaluations + .OrderByDescending(e => e.CreatedOn) + .ToListAsync(ct) + .ConfigureAwait(false); + } + + public async Task GetByIdAsync(System.Guid id, CancellationToken ct) + { + return await _db.ServiceEvaluations + .FirstOrDefaultAsync(e => e.Id == id, ct) + .ConfigureAwait(false); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs index cff5f50f..4dee535f 100644 --- a/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs +++ b/backend/src/CCE.Infrastructure/Persistence/CceDbContext.cs @@ -6,6 +6,7 @@ using CCE.Domain.Community; using CCE.Domain.Content; using CCE.Domain.Country; +using CCE.Domain.Evaluation; using CCE.Domain.Identity; using CCE.Domain.InteractiveCity; using CCE.Domain.KnowledgeMaps; @@ -90,6 +91,9 @@ public CceDbContext(DbContextOptions options) : base(options) { } public DbSet ServiceRatings => Set(); public DbSet SearchQueryLogs => Set(); + // ─── Evaluation ─── + public DbSet ServiceEvaluations => Set(); + // ─── Media ─── public DbSet MediaFiles => Set(); @@ -150,6 +154,7 @@ public CceDbContext(DbContextOptions options) : base(options) { } IQueryable ICceDbContext.PolicySections => PolicySections.AsNoTracking(); IQueryable ICceDbContext.OtpVerifications => OtpVerifications.AsNoTracking(); IQueryable ICceDbContext.UserVerifications => UserVerifications.AsNoTracking(); + IQueryable ICceDbContext.ServiceEvaluations => ServiceEvaluations.AsNoTracking(); IQueryable ICceDbContext.MediaFiles => MediaFiles.AsNoTracking(); void ICceDbContext.Add(T entity) where T : class => Set().Add(entity); diff --git a/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs b/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs new file mode 100644 index 00000000..de05511c --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Configurations/Evaluation/ServiceEvaluationConfiguration.cs @@ -0,0 +1,35 @@ +using CCE.Domain.Evaluation; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CCE.Infrastructure.Persistence.Configurations.Evaluation; + +internal sealed class ServiceEvaluationConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(e => e.Id); + builder.Property(e => e.Id).ValueGeneratedNever(); + + builder.Property(e => e.OverallSatisfaction) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.EaseOfUse) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.ContentSuitability) + .IsRequired() + .HasConversion(); + + builder.Property(e => e.Feedback) + .IsRequired() + .HasMaxLength(500); + + builder.Property(e => e.UserId); + + builder.HasIndex(e => e.CreatedOn) + .HasDatabaseName("ix_service_evaluation_created_on"); + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs new file mode 100644 index 00000000..e1d042da --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.Designer.cs @@ -0,0 +1,3758 @@ +// +using System; +using CCE.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CCE.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(CceDbContext))] + [Migration("20260525092623_AddServiceEvaluation")] + partial class AddServiceEvaluation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.1") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CCE.Domain.Audit.AuditEvent", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Action") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("action"); + + b.Property("Actor") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("actor"); + + b.Property("CorrelationId") + .HasColumnType("uniqueidentifier") + .HasColumnName("correlation_id"); + + b.Property("Diff") + .HasColumnType("nvarchar(max)") + .HasColumnName("diff"); + + b.Property("OccurredOn") + .HasColumnType("datetimeoffset") + .HasColumnName("occurred_on"); + + b.Property("Resource") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("resource"); + + b.HasKey("Id") + .HasName("pk_audit_events"); + + b.HasIndex("CorrelationId") + .HasDatabaseName("ix_audit_events_correlation_id"); + + b.HasIndex("Actor", "OccurredOn") + .HasDatabaseName("ix_audit_events_actor_occurred_on"); + + b.ToTable("audit_events", null, t => + { + t.HasTrigger("trg_audit_events_no_update_delete"); + }); + + b.HasAnnotation("SqlServer:UseSqlOutputClause", false); + }); + + modelBuilder.Entity("CCE.Domain.Community.Post", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AnsweredReplyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("answered_reply_id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("content"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsAnswerable") + .HasColumnType("bit") + .HasColumnName("is_answerable"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("TopicId") + .HasColumnType("uniqueidentifier") + .HasColumnName("topic_id"); + + b.HasKey("Id") + .HasName("pk_posts"); + + b.HasIndex("TopicId") + .HasDatabaseName("ix_post_topic_id"); + + b.HasIndex("AuthorId", "CreatedOn") + .HasDatabaseName("ix_post_author_created"); + + b.ToTable("posts", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_post_follows"); + + b.HasIndex("PostId", "UserId") + .IsUnique() + .HasDatabaseName("ux_post_follow_post_user"); + + b.ToTable("post_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostRating", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.Property("RatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("rated_on"); + + b.Property("Stars") + .HasColumnType("int") + .HasColumnName("stars"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_post_ratings"); + + b.HasIndex("PostId", "UserId") + .IsUnique() + .HasDatabaseName("ux_post_rating_post_user"); + + b.ToTable("post_ratings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.PostReply", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(8000) + .HasColumnType("nvarchar(max)") + .HasColumnName("content"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsByExpert") + .HasColumnType("bit") + .HasColumnName("is_by_expert"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("ParentReplyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_reply_id"); + + b.Property("PostId") + .HasColumnType("uniqueidentifier") + .HasColumnName("post_id"); + + b.HasKey("Id") + .HasName("pk_post_replies"); + + b.HasIndex("ParentReplyId") + .HasDatabaseName("ix_post_reply_parent_id"); + + b.HasIndex("PostId") + .HasDatabaseName("ix_post_reply_post_id"); + + b.ToTable("post_replies", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.Topic", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_topics"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_topic_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("topics", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.TopicFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("TopicId") + .HasColumnType("uniqueidentifier") + .HasColumnName("topic_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_topic_follows"); + + b.HasIndex("TopicId", "UserId") + .IsUnique() + .HasDatabaseName("ux_topic_follow_topic_user"); + + b.ToTable("topic_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Community.UserFollow", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FollowedId") + .HasColumnType("uniqueidentifier") + .HasColumnName("followed_id"); + + b.Property("FollowedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("followed_on"); + + b.Property("FollowerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("follower_id"); + + b.HasKey("Id") + .HasName("pk_user_follows"); + + b.HasIndex("FollowerId", "FollowedId") + .IsUnique() + .HasDatabaseName("ux_user_follow_follower_followed"); + + b.ToTable("user_follows", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.AssetFile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("mime_type"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("original_file_name"); + + b.Property("ScannedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("scanned_on"); + + b.Property("SizeBytes") + .HasColumnType("bigint") + .HasColumnName("size_bytes"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("UploadedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("uploaded_on"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("url"); + + b.Property("VirusScanStatus") + .HasColumnType("int") + .HasColumnName("virus_scan_status"); + + b.HasKey("Id") + .HasName("pk_asset_files"); + + b.HasIndex("VirusScanStatus") + .HasDatabaseName("ix_asset_file_scan_status"); + + b.ToTable("asset_files", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Event", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("EndsOn") + .HasColumnType("datetimeoffset") + .HasColumnName("ends_on"); + + b.Property("FeaturedImageUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("featured_image_url"); + + b.Property("ICalUid") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("i_cal_uid"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LocationAr") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("location_ar"); + + b.Property("LocationEn") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("location_en"); + + b.Property("OnlineMeetingUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("online_meeting_url"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("StartsOn") + .HasColumnType("datetimeoffset") + .HasColumnName("starts_on"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_events"); + + b.HasIndex("ICalUid") + .IsUnique() + .HasDatabaseName("ux_event_ical_uid"); + + b.HasIndex("StartsOn") + .HasDatabaseName("ix_event_starts_on"); + + b.ToTable("events", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.HomepageSection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("SectionType") + .HasColumnType("int") + .HasColumnName("section_type"); + + b.HasKey("Id") + .HasName("pk_homepage_sections"); + + b.HasIndex("IsActive", "OrderIndex") + .HasDatabaseName("ix_homepage_section_active_order"); + + b.ToTable("homepage_sections", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.News", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AuthorId") + .HasColumnType("uniqueidentifier") + .HasColumnName("author_id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("FeaturedImageUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("featured_image_url"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsFeatured") + .HasColumnType("bit") + .HasColumnName("is_featured"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PublishedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("published_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("slug"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_news"); + + b.HasIndex("PublishedOn") + .HasDatabaseName("ix_news_published_on"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_news_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("news", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.NewsletterSubscription", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ConfirmationToken") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("confirmation_token"); + + b.Property("ConfirmedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("confirmed_on"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(320) + .HasColumnType("nvarchar(320)") + .HasColumnName("email"); + + b.Property("IsConfirmed") + .HasColumnType("bit") + .HasColumnName("is_confirmed"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LocalePreference") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale_preference"); + + b.Property("UnsubscribedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("unsubscribed_on"); + + b.HasKey("Id") + .HasName("pk_newsletter_subscriptions"); + + b.HasIndex("ConfirmationToken") + .HasDatabaseName("ix_newsletter_token"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("ux_newsletter_email"); + + b.ToTable("newsletter_subscriptions", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Page", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b.Property("ContentEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PageType") + .HasColumnType("int") + .HasColumnName("page_type"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("slug"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.HasKey("Id") + .HasName("pk_pages"); + + b.HasIndex("PageType", "Slug") + .IsUnique() + .HasDatabaseName("ux_page_type_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("pages", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.Resource", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssetFileId") + .HasColumnType("uniqueidentifier") + .HasColumnName("asset_file_id"); + + b.Property("CategoryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("category_id"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("PublishedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("published_on"); + + b.Property("ResourceType") + .HasColumnType("int") + .HasColumnName("resource_type"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("TitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("title_en"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("ViewCount") + .HasColumnType("bigint") + .HasColumnName("view_count"); + + b.HasKey("Id") + .HasName("pk_resources"); + + b.HasIndex("AssetFileId") + .HasDatabaseName("ix_resource_asset_file_id"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_resource_country_id"); + + b.HasIndex("CategoryId", "PublishedOn") + .HasDatabaseName("ix_resource_category_published"); + + b.ToTable("resources", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Content.ResourceCategory", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("ParentId") + .HasColumnType("uniqueidentifier") + .HasColumnName("parent_id"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_resource_categories"); + + b.HasIndex("ParentId") + .HasDatabaseName("ix_resource_category_parent_id"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_resource_category_slug"); + + b.ToTable("resource_categories", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.Country", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("FlagUrl") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("flag_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsoAlpha2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("iso_alpha2"); + + b.Property("IsoAlpha3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)") + .HasColumnName("iso_alpha3"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LatestKapsarcSnapshotId") + .HasColumnType("uniqueidentifier") + .HasColumnName("latest_kapsarc_snapshot_id"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("RegionAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("region_ar"); + + b.Property("RegionEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("region_en"); + + b.HasKey("Id") + .HasName("pk_countries"); + + b.HasIndex("IsoAlpha2") + .HasDatabaseName("ix_country_iso_alpha2"); + + b.HasIndex("IsoAlpha3") + .IsUnique() + .HasDatabaseName("ux_country_iso_alpha3_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("countries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryKapsarcSnapshot", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Classification") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("classification"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("PerformanceScore") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)") + .HasColumnName("performance_score"); + + b.Property("SnapshotTakenOn") + .HasColumnType("datetimeoffset") + .HasColumnName("snapshot_taken_on"); + + b.Property("SourceVersion") + .HasMaxLength(32) + .HasColumnType("nvarchar(32)") + .HasColumnName("source_version"); + + b.Property("TotalIndex") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)") + .HasColumnName("total_index"); + + b.HasKey("Id") + .HasName("pk_country_kapsarc_snapshots"); + + b.HasIndex("CountryId", "SnapshotTakenOn") + .HasDatabaseName("ix_kapsarc_snapshot_country_taken"); + + b.ToTable("country_kapsarc_snapshots", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryProfile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContactInfoAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("contact_info_ar"); + + b.Property("ContactInfoEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("contact_info_en"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("KeyInitiativesAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("key_initiatives_ar"); + + b.Property("KeyInitiativesEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("key_initiatives_en"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_country_profiles"); + + b.HasIndex("CountryId") + .IsUnique() + .HasDatabaseName("ux_country_profile_country_id"); + + b.ToTable("country_profiles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Country.CountryResourceRequest", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AdminNotesAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("admin_notes_ar"); + + b.Property("AdminNotesEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("admin_notes_en"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("ProcessedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("processed_by_id"); + + b.Property("ProcessedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("processed_on"); + + b.Property("ProposedAssetFileId") + .HasColumnType("uniqueidentifier") + .HasColumnName("proposed_asset_file_id"); + + b.Property("ProposedDescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("proposed_description_ar"); + + b.Property("ProposedDescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("proposed_description_en"); + + b.Property("ProposedResourceType") + .HasColumnType("int") + .HasColumnName("proposed_resource_type"); + + b.Property("ProposedTitleAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("proposed_title_ar"); + + b.Property("ProposedTitleEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("proposed_title_en"); + + b.Property("RequestedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("requested_by_id"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.HasKey("Id") + .HasName("pk_country_resource_requests"); + + b.HasIndex("CountryId", "Status") + .HasDatabaseName("ix_country_request_country_status"); + + b.ToTable("country_resource_requests", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Evaluation.ServiceEvaluation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentSuitability") + .HasColumnType("int") + .HasColumnName("content_suitability"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("EaseOfUse") + .HasColumnType("int") + .HasColumnName("ease_of_use"); + + b.Property("Feedback") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("feedback"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OverallSatisfaction") + .HasColumnType("int") + .HasColumnName("overall_satisfaction"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_evaluations"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_service_evaluation_created_on"); + + b.ToTable("service_evaluations", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.ExpertProfile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AcademicTitleAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("academic_title_ar"); + + b.Property("AcademicTitleEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("academic_title_en"); + + b.Property("ApprovedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("approved_by_id"); + + b.Property("ApprovedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("approved_on"); + + b.Property("BioAr") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("bio_ar"); + + b.Property("BioEn") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("bio_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.PrimitiveCollection("ExpertiseTags") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("expertise_tags"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_expert_profiles"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("ux_expert_profile_active_user") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("expert_profiles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.ExpertRegistrationRequest", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("ProcessedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("processed_by_id"); + + b.Property("ProcessedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("processed_on"); + + b.Property("RejectionReasonAr") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("rejection_reason_ar"); + + b.Property("RejectionReasonEn") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("rejection_reason_en"); + + b.Property("RequestedBioAr") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("requested_bio_ar"); + + b.Property("RequestedBioEn") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("requested_bio_en"); + + b.Property("RequestedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("requested_by_id"); + + b.PrimitiveCollection("RequestedTags") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("requested_tags"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.HasKey("Id") + .HasName("pk_expert_registration_requests"); + + b.HasIndex("RequestedById") + .HasDatabaseName("ix_expert_request_requested_by"); + + b.HasIndex("Status") + .HasDatabaseName("ix_expert_request_status"); + + b.ToTable("expert_registration_requests", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.RefreshToken", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("created_at_utc"); + + b.Property("CreatedByIp") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("created_by_ip"); + + b.Property("ExpiresAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("expires_at_utc"); + + b.Property("ReplacedByTokenHash") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("replaced_by_token_hash"); + + b.Property("RevokedAtUtc") + .HasColumnType("datetimeoffset") + .HasColumnName("revoked_at_utc"); + + b.Property("RevokedByIp") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("revoked_by_ip"); + + b.Property("TokenFamilyId") + .HasColumnType("uniqueidentifier") + .HasColumnName("token_family_id"); + + b.Property("TokenHash") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("token_hash"); + + b.Property("UserAgent") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("user_agent"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_refresh_tokens"); + + b.HasIndex("TokenFamilyId") + .HasDatabaseName("ix_refresh_tokens_token_family_id"); + + b.HasIndex("TokenHash") + .IsUnique() + .HasDatabaseName("ux_refresh_tokens_token_hash"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_refresh_tokens_user_id"); + + b.ToTable("refresh_tokens", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)") + .HasColumnName("concurrency_stamp"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_name"); + + b.HasKey("Id") + .HasName("pk_asp_net_roles"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex") + .HasFilter("[normalized_name] IS NOT NULL"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.StateRepresentativeAssignment", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssignedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("assigned_by_id"); + + b.Property("AssignedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("assigned_on"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RevokedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("revoked_by_id"); + + b.Property("RevokedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("revoked_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_state_representative_assignments"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_state_rep_country_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_state_rep_user_id"); + + b.HasIndex("UserId", "CountryId") + .IsUnique() + .HasDatabaseName("ux_state_rep_active_user_country") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("state_representative_assignments", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AccessFailedCount") + .HasColumnType("int") + .HasColumnName("access_failed_count"); + + b.Property("AvatarUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("avatar_url"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("nvarchar(max)") + .HasColumnName("concurrency_stamp"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("email"); + + b.Property("EmailConfirmed") + .HasColumnType("bit") + .HasColumnName("email_confirmed"); + + b.Property("EntraIdObjectId") + .HasColumnType("uniqueidentifier") + .HasColumnName("entra_id_object_id"); + + b.Property("FirstName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("first_name"); + + b.PrimitiveCollection("Interests") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("interests"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("JobTitle") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("job_title"); + + b.Property("KnowledgeLevel") + .HasColumnType("int") + .HasColumnName("knowledge_level"); + + b.Property("LastName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasColumnName("last_name"); + + b.Property("LocalePreference") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale_preference"); + + b.Property("LockoutEnabled") + .HasColumnType("bit") + .HasColumnName("lockout_enabled"); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset") + .HasColumnName("lockout_end"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_email"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("normalized_user_name"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("organization_name"); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)") + .HasColumnName("password_hash"); + + b.Property("PhoneNumber") + .HasColumnType("nvarchar(max)") + .HasColumnName("phone_number"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasColumnName("phone_number_confirmed"); + + b.Property("SecurityStamp") + .HasColumnType("nvarchar(max)") + .HasColumnName("security_stamp"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TwoFactorEnabled") + .HasColumnType("bit") + .HasColumnName("two_factor_enabled"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("user_name"); + + b.HasKey("Id") + .HasName("pk_asp_net_users"); + + b.HasIndex("CountryId") + .HasDatabaseName("ix_users_country_id"); + + b.HasIndex("EntraIdObjectId") + .IsUnique() + .HasDatabaseName("ix_asp_net_users_entra_id_object_id") + .HasFilter("[entra_id_object_id] IS NOT NULL"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex") + .HasFilter("[normalized_user_name] IS NOT NULL"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityScenario", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CityType") + .HasColumnType("int") + .HasColumnName("city_type"); + + b.Property("ConfigurationJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("configuration_json"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("TargetYear") + .HasColumnType("int") + .HasColumnName("target_year"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_city_scenarios"); + + b.HasIndex("UserId", "LastModifiedOn") + .HasDatabaseName("ix_city_scenario_user_modified"); + + b.ToTable("city_scenarios", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityScenarioResult", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ComputedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("computed_at"); + + b.Property("ComputedCarbonNeutralityYear") + .HasColumnType("int") + .HasColumnName("computed_carbon_neutrality_year"); + + b.Property("ComputedTotalCostUsd") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("computed_total_cost_usd"); + + b.Property("EngineVersion") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("engine_version"); + + b.Property("ScenarioId") + .HasColumnType("uniqueidentifier") + .HasColumnName("scenario_id"); + + b.HasKey("Id") + .HasName("pk_city_scenario_results"); + + b.HasIndex("ScenarioId", "ComputedAt") + .HasDatabaseName("ix_city_result_scenario_at"); + + b.ToTable("city_scenario_results", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.InteractiveCity.CityTechnology", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CarbonImpactKgPerYear") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("carbon_impact_kg_per_year"); + + b.Property("CategoryAr") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("category_ar"); + + b.Property("CategoryEn") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("category_en"); + + b.Property("CostUsd") + .HasPrecision(18, 2) + .HasColumnType("decimal(18,2)") + .HasColumnName("cost_usd"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.HasKey("Id") + .HasName("pk_city_technologies"); + + b.HasIndex("IsActive") + .HasDatabaseName("ix_city_tech_is_active"); + + b.ToTable("city_technologies", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMap", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("DescriptionAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("nvarchar(128)") + .HasColumnName("slug"); + + b.HasKey("Id") + .HasName("pk_knowledge_maps"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("ux_knowledge_map_slug_active") + .HasFilter("[is_deleted] = 0"); + + b.ToTable("knowledge_maps", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapAssociation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AssociatedId") + .HasColumnType("uniqueidentifier") + .HasColumnName("associated_id"); + + b.Property("AssociatedType") + .HasColumnType("int") + .HasColumnName("associated_type"); + + b.Property("NodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("node_id"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_associations"); + + b.HasIndex("NodeId", "AssociatedType", "AssociatedId") + .IsUnique() + .HasDatabaseName("ux_km_assoc_node_type_id"); + + b.ToTable("knowledge_map_associations", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapEdge", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("FromNodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("from_node_id"); + + b.Property("MapId") + .HasColumnType("uniqueidentifier") + .HasColumnName("map_id"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("RelationshipType") + .HasColumnType("int") + .HasColumnName("relationship_type"); + + b.Property("ToNodeId") + .HasColumnType("uniqueidentifier") + .HasColumnName("to_node_id"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_edges"); + + b.HasIndex("FromNodeId") + .HasDatabaseName("ix_km_edge_from_node"); + + b.HasIndex("ToNodeId") + .HasDatabaseName("ix_km_edge_to_node"); + + b.HasIndex("MapId", "FromNodeId", "ToNodeId", "RelationshipType") + .IsUnique() + .HasDatabaseName("ux_km_edge_map_from_to_relation"); + + b.ToTable("knowledge_map_edges", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.KnowledgeMaps.KnowledgeMapNode", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("DescriptionAr") + .HasColumnType("nvarchar(max)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .HasColumnType("nvarchar(max)") + .HasColumnName("description_en"); + + b.Property("IconUrl") + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("icon_url"); + + b.Property("LayoutX") + .HasColumnType("float") + .HasColumnName("layout_x"); + + b.Property("LayoutY") + .HasColumnType("float") + .HasColumnName("layout_y"); + + b.Property("MapId") + .HasColumnType("uniqueidentifier") + .HasColumnName("map_id"); + + b.Property("NameAr") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_ar"); + + b.Property("NameEn") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("name_en"); + + b.Property("NodeType") + .HasColumnType("int") + .HasColumnName("node_type"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_knowledge_map_nodes"); + + b.HasIndex("MapId", "OrderIndex") + .HasDatabaseName("ix_km_node_map_order"); + + b.ToTable("knowledge_map_nodes", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Media.MediaFile", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AltTextAr") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("alt_text_ar"); + + b.Property("AltTextEn") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("alt_text_en"); + + b.Property("DescriptionAr") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b.Property("DescriptionEn") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b.Property("MimeType") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("mime_type"); + + b.Property("OriginalFileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("nvarchar(255)") + .HasColumnName("original_file_name"); + + b.Property("SizeBytes") + .HasColumnType("bigint") + .HasColumnName("size_bytes"); + + b.Property("StorageKey") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("storage_key"); + + b.Property("TitleAr") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("title_ar"); + + b.Property("TitleEn") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("title_en"); + + b.Property("UploadedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("uploaded_by_id"); + + b.Property("UploadedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("uploaded_on"); + + b.Property("Url") + .IsRequired() + .HasMaxLength(2048) + .HasColumnType("nvarchar(2048)") + .HasColumnName("url"); + + b.HasKey("Id") + .HasName("pk_media_files"); + + b.ToTable("media_files", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.NotificationLog", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AttemptCount") + .HasColumnType("int") + .HasColumnName("attempt_count"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("CorrelationId") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("correlation_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("Error") + .HasColumnType("nvarchar(max)") + .HasColumnName("error"); + + b.Property("FailedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("failed_on"); + + b.Property("PayloadJson") + .HasColumnType("nvarchar(max)") + .HasColumnName("payload_json"); + + b.Property("ProviderMessageId") + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("provider_message_id"); + + b.Property("RecipientUserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("recipient_user_id"); + + b.Property("SentOn") + .HasColumnType("datetimeoffset") + .HasColumnName("sent_on"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("template_code"); + + b.Property("TemplateId") + .HasColumnType("uniqueidentifier") + .HasColumnName("template_id"); + + b.HasKey("Id") + .HasName("pk_notification_logs"); + + b.HasIndex("CorrelationId") + .HasDatabaseName("ix_notification_log_correlation_id"); + + b.HasIndex("TemplateCode", "Channel") + .HasDatabaseName("ix_notification_log_template_channel"); + + b.HasIndex("RecipientUserId", "Status", "CreatedOn") + .HasDatabaseName("ix_notification_log_recipient_status_created"); + + b.ToTable("notification_logs", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.NotificationTemplate", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("BodyAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("body_ar"); + + b.Property("BodyEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("body_en"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("code"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("is_active"); + + b.Property("SubjectAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("subject_ar"); + + b.Property("SubjectEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("subject_en"); + + b.Property("VariableSchemaJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("variable_schema_json"); + + b.HasKey("Id") + .HasName("pk_notification_templates"); + + b.HasIndex("Code", "Channel") + .IsUnique() + .HasDatabaseName("ux_notification_template_code_channel"); + + b.ToTable("notification_templates", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.UserNotification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("ReadOn") + .HasColumnType("datetimeoffset") + .HasColumnName("read_on"); + + b.Property("RenderedBody") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("rendered_body"); + + b.Property("RenderedLocale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("rendered_locale"); + + b.Property("RenderedSubjectAr") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("rendered_subject_ar"); + + b.Property("RenderedSubjectEn") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("rendered_subject_en"); + + b.Property("SentOn") + .HasColumnType("datetimeoffset") + .HasColumnName("sent_on"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("TemplateId") + .HasColumnType("uniqueidentifier") + .HasColumnName("template_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_notifications"); + + b.HasIndex("UserId", "Status") + .HasDatabaseName("ix_user_notification_user_status"); + + b.ToTable("user_notifications", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Notifications.UserNotificationSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Channel") + .HasColumnType("int") + .HasColumnName("channel"); + + b.Property("EventCode") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasColumnName("event_code"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasColumnName("is_enabled"); + + b.Property("UpdatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("updated_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_user_notification_settings"); + + b.HasIndex("UserId", "Channel", "EventCode") + .IsUnique() + .HasDatabaseName("ux_user_notification_settings_user_channel_event") + .HasFilter("[event_code] IS NOT NULL"); + + b.ToTable("user_notification_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("HowToUseVideoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("how_to_use_video_url"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_about_settings"); + + b.ToTable("about_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("about_settings_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_glossary_entries"); + + b.HasIndex("AboutSettingsId") + .HasDatabaseName("ix_glossary_entries_about_settings_id"); + + b.ToTable("glossary_entries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageCountry", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CountryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("country_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("HomepageSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("homepage_settings_id"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.HasKey("Id") + .HasName("pk_homepage_countries"); + + b.HasIndex("HomepageSettingsId", "CountryId") + .IsUnique() + .HasDatabaseName("ix_homepage_country_settings_country"); + + b.ToTable("homepage_countries", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CceConceptsAr") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("cce_concepts_ar"); + + b.Property("CceConceptsEn") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("cce_concepts_en"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.Property("VideoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("video_url"); + + b.HasKey("Id") + .HasName("pk_homepage_settings"); + + b.ToTable("homepage_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.KnowledgePartner", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("about_settings_id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LogoUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("logo_url"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("WebsiteUrl") + .HasColumnType("nvarchar(max)") + .HasColumnName("website_url"); + + b.HasKey("Id") + .HasName("pk_knowledge_partners"); + + b.HasIndex("AboutSettingsId") + .HasDatabaseName("ix_knowledge_partners_about_settings_id"); + + b.ToTable("knowledge_partners", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PoliciesSettings", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion") + .HasColumnName("row_version"); + + b.HasKey("Id") + .HasName("pk_policies_settings"); + + b.ToTable("policies_settings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PolicySection", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OrderIndex") + .HasColumnType("int") + .HasColumnName("order_index"); + + b.Property("PoliciesSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("policies_settings_id"); + + b.Property("Type") + .HasColumnType("int") + .HasColumnName("type"); + + b.HasKey("Id") + .HasName("pk_policy_sections"); + + b.HasIndex("PoliciesSettingsId") + .HasDatabaseName("ix_policy_sections_policies_settings_id"); + + b.ToTable("policy_sections", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Surveys.SearchQueryLog", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("QueryText") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("query_text"); + + b.Property("ResponseTimeMs") + .HasColumnType("int") + .HasColumnName("response_time_ms"); + + b.Property("ResultsCount") + .HasColumnType("int") + .HasColumnName("results_count"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_search_query_logs"); + + b.HasIndex("SubmittedOn") + .HasDatabaseName("ix_search_query_log_submitted_on"); + + b.ToTable("search_query_logs", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Surveys.ServiceRating", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("CommentAr") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("comment_ar"); + + b.Property("CommentEn") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)") + .HasColumnName("comment_en"); + + b.Property("Locale") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)") + .HasColumnName("locale"); + + b.Property("Page") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("page"); + + b.Property("Rating") + .HasColumnType("int") + .HasColumnName("rating"); + + b.Property("SubmittedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("submitted_on"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_ratings"); + + b.HasIndex("SubmittedOn") + .HasDatabaseName("ix_service_rating_submitted_on"); + + b.ToTable("service_ratings", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Verification.OtpVerification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("AttemptCount") + .HasColumnType("int") + .HasColumnName("attempt_count"); + + b.Property("CodeHash") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("nvarchar(512)") + .HasColumnName("code_hash"); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("contact"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("created_at"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("ExpiresAt") + .HasColumnType("datetimeoffset") + .HasColumnName("expires_at"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsInvalidated") + .HasColumnType("bit") + .HasColumnName("is_invalidated"); + + b.Property("IsVerified") + .HasColumnType("bit") + .HasColumnName("is_verified"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("LastSentAt") + .HasColumnType("datetimeoffset") + .HasColumnName("last_sent_at"); + + b.Property("TypeId") + .HasColumnType("int") + .HasColumnName("type_id"); + + b.HasKey("Id") + .HasName("pk_otp_verifications"); + + b.HasIndex("Contact", "TypeId") + .HasDatabaseName("ix_otp_verifications_contact_type_id"); + + b.ToTable("otp_verifications", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Verification.UserVerification", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("Contact") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)") + .HasColumnName("contact"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("DeletedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("deleted_by_id"); + + b.Property("DeletedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("deleted_on"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasColumnName("is_deleted"); + + b.Property("IsVerified") + .HasColumnType("bit") + .HasColumnName("is_verified"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("TypeId") + .HasColumnType("int") + .HasColumnName("type_id"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("VerifiedAt") + .HasColumnType("datetimeoffset") + .HasColumnName("verified_at"); + + b.HasKey("Id") + .HasName("pk_user_verifications"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_user_verifications_user_id"); + + b.HasIndex("Contact", "TypeId") + .IsUnique() + .HasDatabaseName("ix_user_verifications_contact_type_id"); + + b.ToTable("user_verifications", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_type"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_value"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier") + .HasColumnName("role_id"); + + b.HasKey("Id") + .HasName("pk_asp_net_role_claims"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_asp_net_role_claims_role_id"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasColumnName("id"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_type"); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(max)") + .HasColumnName("claim_value"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_asp_net_user_claims"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_asp_net_user_claims_user_id"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)") + .HasColumnName("login_provider"); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(450)") + .HasColumnName("provider_key"); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(max)") + .HasColumnName("provider_display_name"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("LoginProvider", "ProviderKey") + .HasName("pk_asp_net_user_logins"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_asp_net_user_logins_user_id"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier") + .HasColumnName("role_id"); + + b.HasKey("UserId", "RoleId") + .HasName("pk_asp_net_user_roles"); + + b.HasIndex("RoleId") + .HasDatabaseName("ix_asp_net_user_roles_role_id"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(450)") + .HasColumnName("login_provider"); + + b.Property("Name") + .HasColumnType("nvarchar(450)") + .HasColumnName("name"); + + b.Property("Value") + .HasColumnType("nvarchar(max)") + .HasColumnName("value"); + + b.HasKey("UserId", "LoginProvider", "Name") + .HasName("pk_asp_net_user_tokens"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("CCE.Domain.Identity.RefreshToken", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_refresh_tokens_asp_net_users_user_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Description", b1 => + { + b1.Property("AboutSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b1.HasKey("AboutSettingsId"); + + b1.ToTable("about_settings"); + + b1.WithOwner() + .HasForeignKey("AboutSettingsId") + .HasConstraintName("fk_about_settings_about_settings_id"); + }); + + b.Navigation("Description") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.GlossaryEntry", b => + { + b.HasOne("CCE.Domain.PlatformSettings.AboutSettings", null) + .WithMany("GlossaryEntries") + .HasForeignKey("AboutSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_glossary_entries_about_settings_about_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Definition", b1 => + { + b1.Property("GlossaryEntryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("definition_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("definition_en"); + + b1.HasKey("GlossaryEntryId"); + + b1.ToTable("glossary_entries"); + + b1.WithOwner() + .HasForeignKey("GlossaryEntryId") + .HasConstraintName("fk_glossary_entries_glossary_entries_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Term", b1 => + { + b1.Property("GlossaryEntryId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("term_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasColumnName("term_en"); + + b1.HasKey("GlossaryEntryId"); + + b1.ToTable("glossary_entries"); + + b1.WithOwner() + .HasForeignKey("GlossaryEntryId") + .HasConstraintName("fk_glossary_entries_glossary_entries_id"); + }); + + b.Navigation("Definition") + .IsRequired(); + + b.Navigation("Term") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageCountry", b => + { + b.HasOne("CCE.Domain.PlatformSettings.HomepageSettings", null) + .WithMany("Countries") + .HasForeignKey("HomepageSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_homepage_countries_homepage_settings_homepage_settings_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Objective", b1 => + { + b1.Property("HomepageSettingsId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("objective_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("objective_en"); + + b1.HasKey("HomepageSettingsId"); + + b1.ToTable("homepage_settings"); + + b1.WithOwner() + .HasForeignKey("HomepageSettingsId") + .HasConstraintName("fk_homepage_settings_homepage_settings_id"); + }); + + b.Navigation("Objective") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.KnowledgePartner", b => + { + b.HasOne("CCE.Domain.PlatformSettings.AboutSettings", null) + .WithMany("KnowledgePartners") + .HasForeignKey("AboutSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_knowledge_partners_about_settings_about_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Description", b1 => + { + b1.Property("KnowledgePartnerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)") + .HasColumnName("description_en"); + + b1.HasKey("KnowledgePartnerId"); + + b1.ToTable("knowledge_partners"); + + b1.WithOwner() + .HasForeignKey("KnowledgePartnerId") + .HasConstraintName("fk_knowledge_partners_knowledge_partners_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Name", b1 => + { + b1.Property("KnowledgePartnerId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("name_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)") + .HasColumnName("name_en"); + + b1.HasKey("KnowledgePartnerId"); + + b1.ToTable("knowledge_partners"); + + b1.WithOwner() + .HasForeignKey("KnowledgePartnerId") + .HasConstraintName("fk_knowledge_partners_knowledge_partners_id"); + }); + + b.Navigation("Description"); + + b.Navigation("Name") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PolicySection", b => + { + b.HasOne("CCE.Domain.PlatformSettings.PoliciesSettings", null) + .WithMany("Sections") + .HasForeignKey("PoliciesSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_policy_sections_policies_settings_policies_settings_id"); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Content", b1 => + { + b1.Property("PolicySectionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_ar"); + + b1.Property("En") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasColumnName("content_en"); + + b1.HasKey("PolicySectionId"); + + b1.ToTable("policy_sections"); + + b1.WithOwner() + .HasForeignKey("PolicySectionId") + .HasConstraintName("fk_policy_sections_policy_sections_id"); + }); + + b.OwnsOne("CCE.Domain.PlatformSettings.ValueObjects.LocalizedText", "Title", b1 => + { + b1.Property("PolicySectionId") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b1.Property("Ar") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("title_ar"); + + b1.Property("En") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("title_en"); + + b1.HasKey("PolicySectionId"); + + b1.ToTable("policy_sections"); + + b1.WithOwner() + .HasForeignKey("PolicySectionId") + .HasConstraintName("fk_policy_sections_policy_sections_id"); + }); + + b.Navigation("Content") + .IsRequired(); + + b.Navigation("Title") + .IsRequired(); + }); + + modelBuilder.Entity("CCE.Domain.Verification.UserVerification", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_user_verifications_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("CCE.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_role_claims_asp_net_roles_role_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_claims_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_logins_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("CCE.Domain.Identity.Role", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_roles_asp_net_roles_role_id"); + + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_roles_asp_net_users_user_id"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("CCE.Domain.Identity.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_asp_net_user_tokens_asp_net_users_user_id"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.AboutSettings", b => + { + b.Navigation("GlossaryEntries"); + + b.Navigation("KnowledgePartners"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.HomepageSettings", b => + { + b.Navigation("Countries"); + }); + + modelBuilder.Entity("CCE.Domain.PlatformSettings.PoliciesSettings", b => + { + b.Navigation("Sections"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs new file mode 100644 index 00000000..2ab9c12b --- /dev/null +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs @@ -0,0 +1,47 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CCE.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddServiceEvaluation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "service_evaluations", + columns: table => new + { + id = table.Column(type: "uniqueidentifier", nullable: false), + overall_satisfaction = table.Column(type: "int", nullable: false), + ease_of_use = table.Column(type: "int", nullable: false), + content_suitability = table.Column(type: "int", nullable: false), + feedback = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + user_id = table.Column(type: "uniqueidentifier", nullable: true), + created_on = table.Column(type: "datetimeoffset", nullable: false), + created_by_id = table.Column(type: "uniqueidentifier", nullable: 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_service_evaluations", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "ix_service_evaluation_created_on", + table: "service_evaluations", + column: "created_on"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "service_evaluations"); + } + } +} diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs index eebf84ba..53a79692 100644 --- a/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/CceDbContextModelSnapshot.cs @@ -1395,6 +1395,59 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("country_resource_requests", (string)null); }); + modelBuilder.Entity("CCE.Domain.Evaluation.ServiceEvaluation", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier") + .HasColumnName("id"); + + b.Property("ContentSuitability") + .HasColumnType("int") + .HasColumnName("content_suitability"); + + b.Property("CreatedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("created_by_id"); + + b.Property("CreatedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("created_on"); + + b.Property("EaseOfUse") + .HasColumnType("int") + .HasColumnName("ease_of_use"); + + b.Property("Feedback") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasColumnName("feedback"); + + b.Property("LastModifiedById") + .HasColumnType("uniqueidentifier") + .HasColumnName("last_modified_by_id"); + + b.Property("LastModifiedOn") + .HasColumnType("datetimeoffset") + .HasColumnName("last_modified_on"); + + b.Property("OverallSatisfaction") + .HasColumnType("int") + .HasColumnName("overall_satisfaction"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_service_evaluations"); + + b.HasIndex("CreatedOn") + .HasDatabaseName("ix_service_evaluation_created_on"); + + b.ToTable("service_evaluations", (string)null); + }); + modelBuilder.Entity("CCE.Domain.Identity.ExpertProfile", b => { b.Property("Id") From 9454c6c4dc82b0f7c7d8c7bfc11276c13b2adf2d Mon Sep 17 00:00:00 2001 From: ahmed Date: Mon, 25 May 2026 19:16:30 +0300 Subject: [PATCH 2/5] feat:Fix Pattern of Service Evaluation --- .../Localization/Resources.yaml | 12 +++++++++ .../Middleware/ExceptionHandlingMiddleware.cs | 3 ++- .../Endpoints/EvaluationEndpoints.cs | 21 ++++++++++----- .../Endpoints/EvaluationEndpoints.cs | 5 ++-- .../Behaviors/ResponseValidationBehavior.cs | 3 ++- .../SubmitEvaluationCommandValidator.cs | 16 ++++++++--- .../Evaluation/IEvaluationRepository.cs | 2 -- .../GetAllEvaluationsQuery.cs | 3 ++- .../GetAllEvaluationsQueryHandler.cs | 25 ++++++++++++----- .../GetEvaluationByIdQuery.cs | 3 ++- .../GetEvaluationByIdQueryHandler.cs | 27 +++++++++++++------ .../Evaluation/EvaluationRepository.cs | 16 ----------- 12 files changed, 87 insertions(+), 49 deletions(-) diff --git a/backend/src/CCE.Api.Common/Localization/Resources.yaml b/backend/src/CCE.Api.Common/Localization/Resources.yaml index 853039f8..e3f70d4b 100644 --- a/backend/src/CCE.Api.Common/Localization/Resources.yaml +++ b/backend/src/CCE.Api.Common/Localization/Resources.yaml @@ -162,6 +162,10 @@ VALIDATION_REQUIRED_FIELD: ar: "هذا الحقل مطلوب" en: "This field is required" +REQUIRED_FIELD: + ar: "هذا الحقل مطلوب" + en: "This field is required" + VALIDATION_INVALID_EMAIL: ar: "البريد الإلكتروني غير صالح" en: "Invalid email format" @@ -174,6 +178,10 @@ VALIDATION_MAX_LENGTH: ar: "القيمة طويلة جدًا" en: "Value is too long" +MAX_LENGTH: + ar: "القيمة طويلة جدًا" + en: "Value is too long" + VALIDATION_INVALID_FORMAT: ar: "التنسيق غير صالح" en: "Invalid format" @@ -182,6 +190,10 @@ VALIDATION_INVALID_ENUM: ar: "القيمة المحددة غير صالحة" en: "Selected value is invalid" +INVALID_ENUM: + ar: "القيمة المحددة غير صالحة" + en: "Selected value is invalid" + # ─── Identity Bare Keys (errors) ─── USER_NOT_FOUND: diff --git a/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs b/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs index 77a2eb63..354e17b3 100644 --- a/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs +++ b/backend/src/CCE.Api.Common/Middleware/ExceptionHandlingMiddleware.cs @@ -93,9 +93,10 @@ private static async Task WriteValidationResultAsync(HttpContext ctx, Validation var fieldErrors = ex.Errors.Select(e => { - var domainKey = e.ErrorMessage; + var domainKey = e.ErrorCode; var valCode = SystemCodeMap.ToSystemCode(domainKey); var valMsg = l?.GetString(domainKey) ?? domainKey; + if (valMsg == domainKey) valMsg = e.ErrorMessage; return new { field = ToCamelCase(e.PropertyName), diff --git a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs index a25620fe..5823b9dd 100644 --- a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs +++ b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs @@ -20,10 +20,19 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu IMediator mediator, CancellationToken ct) => { + if (!Enum.IsDefined(typeof(EvaluationRating), body.OverallSatisfaction) || body.OverallSatisfaction == 0) + return Results.BadRequest(new { error = "OverallSatisfaction must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); + + if (!Enum.IsDefined(typeof(EvaluationRating), body.EaseOfUse) || body.EaseOfUse == 0) + return Results.BadRequest(new { error = "EaseOfUse must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); + + if (!Enum.IsDefined(typeof(EvaluationRating), body.ContentSuitability) || body.ContentSuitability == 0) + return Results.BadRequest(new { error = "ContentSuitability must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); + var cmd = new SubmitEvaluationCommand( - body.OverallSatisfaction, - body.EaseOfUse, - body.ContentSuitability, + (EvaluationRating)body.OverallSatisfaction, + (EvaluationRating)body.EaseOfUse, + (EvaluationRating)body.ContentSuitability, body.Feedback); var result = await mediator.Send(cmd, ct).ConfigureAwait(false); return result.ToHttpResult(StatusCodes.Status201Created); @@ -36,7 +45,7 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu } public sealed record SubmitEvaluationRequest( - EvaluationRating OverallSatisfaction, - EvaluationRating EaseOfUse, - EvaluationRating ContentSuitability, + int OverallSatisfaction, + int EaseOfUse, + int ContentSuitability, string Feedback); diff --git a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs index ecaa86bd..9db5151b 100644 --- a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs +++ b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs @@ -1,3 +1,4 @@ +using CCE.Api.Common.Extensions; using CCE.Application.Evaluation.Queries.GetAllEvaluations; using CCE.Application.Evaluation.Queries.GetEvaluationById; using CCE.Domain; @@ -20,7 +21,7 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu CancellationToken ct) => { var result = await mediator.Send(new GetAllEvaluationsQuery(), ct).ConfigureAwait(false); - return Results.Ok(result); + return result.ToHttpResult(); }) .RequireAuthorization(Permissions.Survey_ReadAll) .WithName("GetAllEvaluations"); @@ -32,7 +33,7 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu CancellationToken ct) => { var result = await mediator.Send(new GetEvaluationByIdQuery(id), ct).ConfigureAwait(false); - return result is null ? Results.NotFound() : Results.Ok(result); + return result.ToHttpResult(); }) .RequireAuthorization(Permissions.Survey_ReadAll) .WithName("GetEvaluationById"); diff --git a/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs b/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs index 920459b4..769af6c3 100644 --- a/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs +++ b/backend/src/CCE.Application/Common/Behaviors/ResponseValidationBehavior.cs @@ -47,9 +47,10 @@ public async Task Handle( { var fieldErrors = failures.Select(f => { - var domainKey = f.ErrorMessage; + var domainKey = f.ErrorCode; var valCode = SystemCodeMap.ToSystemCode(domainKey); var msg = _l.GetString(domainKey); + if (msg == domainKey) msg = f.ErrorMessage; return new FieldError( ToCamelCase(f.PropertyName), valCode, diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs index 8da2de7a..395629f0 100644 --- a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs @@ -7,9 +7,17 @@ public sealed class SubmitEvaluationCommandValidator : AbstractValidator x.OverallSatisfaction).IsInEnum().NotEqual(EvaluationRating.None); - RuleFor(x => x.EaseOfUse).IsInEnum().NotEqual(EvaluationRating.None); - RuleFor(x => x.ContentSuitability).IsInEnum().NotEqual(EvaluationRating.None); - RuleFor(x => x.Feedback).NotEmpty().MaximumLength(500); + RuleFor(x => x.OverallSatisfaction) + .IsInEnum().WithErrorCode("INVALID_ENUM") + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.EaseOfUse) + .IsInEnum().WithErrorCode("INVALID_ENUM") + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.ContentSuitability) + .IsInEnum().WithErrorCode("INVALID_ENUM") + .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); + RuleFor(x => x.Feedback) + .NotEmpty().WithErrorCode("REQUIRED_FIELD") + .MaximumLength(500).WithErrorCode("MAX_LENGTH"); } } diff --git a/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs index e382ffae..98435889 100644 --- a/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs +++ b/backend/src/CCE.Application/Evaluation/IEvaluationRepository.cs @@ -5,6 +5,4 @@ namespace CCE.Application.Evaluation; public interface IEvaluationRepository { Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct); - Task> GetAllAsync(CancellationToken ct); - Task GetByIdAsync(System.Guid id, CancellationToken ct); } diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs index 2249a409..22c3e04b 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs @@ -1,6 +1,7 @@ +using CCE.Application.Common; using CCE.Application.Evaluation.DTOs; using MediatR; namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; -public sealed record GetAllEvaluationsQuery : IRequest>; +public sealed record GetAllEvaluationsQuery : IRequest>>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs index fb41b435..6544d843 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs @@ -1,25 +1,34 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; using CCE.Application.Evaluation.DTOs; +using CCE.Application.Messages; using MediatR; +using Microsoft.EntityFrameworkCore; namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; public sealed class GetAllEvaluationsQueryHandler - : IRequestHandler> + : IRequestHandler>> { - private readonly IEvaluationRepository _repository; + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; - public GetAllEvaluationsQueryHandler(IEvaluationRepository repository) + public GetAllEvaluationsQueryHandler(ICceDbContext db, MessageFactory msg) { - _repository = repository; + _db = db; + _msg = msg; } - public async Task> Handle( + public async Task>> Handle( GetAllEvaluationsQuery request, CancellationToken cancellationToken) { - var evaluations = await _repository.GetAllAsync(cancellationToken).ConfigureAwait(false); + var evaluations = await _db.ServiceEvaluations + .OrderByDescending(e => e.CreatedOn) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); - return evaluations.Select(e => new ServiceEvaluationDto( + var dtos = evaluations.Select(e => new ServiceEvaluationDto( e.Id, e.OverallSatisfaction, e.EaseOfUse, @@ -28,5 +37,7 @@ public async Task> Handle( e.UserId, e.CreatedOn, e.CreatedById)).ToList(); + + return _msg.Ok(dtos, "ITEMS_LISTED"); } } diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs index 6bba2c74..15e77c0f 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQuery.cs @@ -1,6 +1,7 @@ +using CCE.Application.Common; using CCE.Application.Evaluation.DTOs; using MediatR; namespace CCE.Application.Evaluation.Queries.GetEvaluationById; -public sealed record GetEvaluationByIdQuery(System.Guid Id) : IRequest; +public sealed record GetEvaluationByIdQuery(System.Guid Id) : IRequest>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs index f6874550..f8e8f18d 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetEvaluationById/GetEvaluationByIdQueryHandler.cs @@ -1,27 +1,36 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; using CCE.Application.Evaluation.DTOs; +using CCE.Application.Messages; using MediatR; +using Microsoft.EntityFrameworkCore; namespace CCE.Application.Evaluation.Queries.GetEvaluationById; public sealed class GetEvaluationByIdQueryHandler - : IRequestHandler + : IRequestHandler> { - private readonly IEvaluationRepository _repository; + private readonly ICceDbContext _db; + private readonly MessageFactory _msg; - public GetEvaluationByIdQueryHandler(IEvaluationRepository repository) + public GetEvaluationByIdQueryHandler(ICceDbContext db, MessageFactory msg) { - _repository = repository; + _db = db; + _msg = msg; } - public async Task Handle( + public async Task> Handle( GetEvaluationByIdQuery request, CancellationToken cancellationToken) { - var evaluation = await _repository.GetByIdAsync(request.Id, cancellationToken).ConfigureAwait(false); + var evaluation = await _db.ServiceEvaluations + .FirstOrDefaultAsync(e => e.Id == request.Id, cancellationToken) + .ConfigureAwait(false); - if (evaluation is null) return null; + if (evaluation is null) + return _msg.EvaluationNotFound(); - return new ServiceEvaluationDto( + var dto = new ServiceEvaluationDto( evaluation.Id, evaluation.OverallSatisfaction, evaluation.EaseOfUse, @@ -30,5 +39,7 @@ public GetEvaluationByIdQueryHandler(IEvaluationRepository repository) evaluation.UserId, evaluation.CreatedOn, evaluation.CreatedById); + + return _msg.Ok(dto, "ITEMS_LISTED"); } } diff --git a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs index 7dcbbf35..b8900efc 100644 --- a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs +++ b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs @@ -1,7 +1,6 @@ using CCE.Application.Evaluation; using CCE.Domain.Evaluation; using CCE.Infrastructure.Persistence; -using Microsoft.EntityFrameworkCore; namespace CCE.Infrastructure.Evaluation; @@ -19,19 +18,4 @@ public async Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct) _db.ServiceEvaluations.Add(evaluation); await _db.SaveChangesAsync(ct).ConfigureAwait(false); } - - public async Task> GetAllAsync(CancellationToken ct) - { - return await _db.ServiceEvaluations - .OrderByDescending(e => e.CreatedOn) - .ToListAsync(ct) - .ConfigureAwait(false); - } - - public async Task GetByIdAsync(System.Guid id, CancellationToken ct) - { - return await _db.ServiceEvaluations - .FirstOrDefaultAsync(e => e.Id == id, ct) - .ConfigureAwait(false); - } } From ece773271092089bd50ae06aac395b586c9ff400 Mon Sep 17 00:00:00 2001 From: ahmed Date: Tue, 26 May 2026 15:38:17 +0300 Subject: [PATCH 3/5] chore: remove temporary migration SQL script --- backend/AddServiceEvaluation.sql | 1428 ------------------------------ 1 file changed, 1428 deletions(-) delete mode 100644 backend/AddServiceEvaluation.sql diff --git a/backend/AddServiceEvaluation.sql b/backend/AddServiceEvaluation.sql deleted file mode 100644 index e4728d13..00000000 --- a/backend/AddServiceEvaluation.sql +++ /dev/null @@ -1,1428 +0,0 @@ -IF OBJECT_ID(N'[__EFMigrationsHistory]') IS NULL -BEGIN - CREATE TABLE [__EFMigrationsHistory] ( - [migration_id] nvarchar(150) NOT NULL, - [product_version] nvarchar(32) NOT NULL, - CONSTRAINT [pk___ef_migrations_history] PRIMARY KEY ([migration_id]) - ); -END; -GO - -BEGIN TRANSACTION; -CREATE TABLE [audit_events] ( - [id] uniqueidentifier NOT NULL, - [occurred_on] datetimeoffset NOT NULL, - [actor] nvarchar(256) NOT NULL, - [action] nvarchar(128) NOT NULL, - [resource] nvarchar(512) NOT NULL, - [correlation_id] uniqueidentifier NOT NULL, - [diff] nvarchar(max) NULL, - CONSTRAINT [pk_audit_events] PRIMARY KEY ([id]) -); - -CREATE INDEX [ix_audit_events_actor_occurred_on] ON [audit_events] ([actor], [occurred_on]); - -CREATE INDEX [ix_audit_events_correlation_id] ON [audit_events] ([correlation_id]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260425134009_InitialAuditEvents', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; - -CREATE TRIGGER trg_audit_events_no_update_delete -ON dbo.audit_events -INSTEAD OF UPDATE, DELETE -AS -BEGIN - THROW 51000, 'audit_events is append-only; UPDATE and DELETE are not permitted.', 1; -END; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260425134559_AuditEventsAppendOnlyTrigger', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -CREATE TABLE [AspNetRoles] ( - [id] uniqueidentifier NOT NULL, - [name] nvarchar(256) NULL, - [normalized_name] nvarchar(256) NULL, - [concurrency_stamp] nvarchar(max) NULL, - CONSTRAINT [pk_asp_net_roles] PRIMARY KEY ([id]) -); - -CREATE TABLE [AspNetUsers] ( - [id] uniqueidentifier NOT NULL, - [locale_preference] nvarchar(2) NOT NULL, - [knowledge_level] int NOT NULL, - [interests] nvarchar(max) NOT NULL, - [country_id] uniqueidentifier NULL, - [avatar_url] nvarchar(2048) NULL, - [user_name] nvarchar(256) NULL, - [normalized_user_name] nvarchar(256) NULL, - [email] nvarchar(256) NULL, - [normalized_email] nvarchar(256) NULL, - [email_confirmed] bit NOT NULL, - [password_hash] nvarchar(max) NULL, - [security_stamp] nvarchar(max) NULL, - [concurrency_stamp] nvarchar(max) NULL, - [phone_number] nvarchar(max) NULL, - [phone_number_confirmed] bit NOT NULL, - [two_factor_enabled] bit NOT NULL, - [lockout_end] datetimeoffset NULL, - [lockout_enabled] bit NOT NULL, - [access_failed_count] int NOT NULL, - CONSTRAINT [pk_asp_net_users] PRIMARY KEY ([id]) -); - -CREATE TABLE [asset_files] ( - [id] uniqueidentifier NOT NULL, - [url] nvarchar(2048) NOT NULL, - [original_file_name] nvarchar(512) NOT NULL, - [size_bytes] bigint NOT NULL, - [mime_type] nvarchar(128) NOT NULL, - [uploaded_by_id] uniqueidentifier NOT NULL, - [uploaded_on] datetimeoffset NOT NULL, - [virus_scan_status] int NOT NULL, - [scanned_on] datetimeoffset NULL, - CONSTRAINT [pk_asset_files] PRIMARY KEY ([id]) -); - -CREATE TABLE [city_scenario_results] ( - [id] uniqueidentifier NOT NULL, - [scenario_id] uniqueidentifier NOT NULL, - [computed_carbon_neutrality_year] int NULL, - [computed_total_cost_usd] decimal(18,2) NOT NULL, - [computed_at] datetimeoffset NOT NULL, - [engine_version] nvarchar(64) NOT NULL, - CONSTRAINT [pk_city_scenario_results] PRIMARY KEY ([id]) -); - -CREATE TABLE [city_scenarios] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [city_type] int NOT NULL, - [target_year] int NOT NULL, - [configuration_json] nvarchar(max) NOT NULL, - [created_on] datetimeoffset NOT NULL, - [last_modified_on] datetimeoffset NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_city_scenarios] PRIMARY KEY ([id]) -); - -CREATE TABLE [city_technologies] ( - [id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [category_ar] nvarchar(128) NOT NULL, - [category_en] nvarchar(128) NOT NULL, - [carbon_impact_kg_per_year] decimal(18,2) NOT NULL, - [cost_usd] decimal(18,2) NOT NULL, - [icon_url] nvarchar(2048) NULL, - [is_active] bit NOT NULL, - CONSTRAINT [pk_city_technologies] PRIMARY KEY ([id]) -); - -CREATE TABLE [countries] ( - [id] uniqueidentifier NOT NULL, - [iso_alpha3] nvarchar(3) NOT NULL, - [iso_alpha2] nvarchar(2) NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [region_ar] nvarchar(128) NOT NULL, - [region_en] nvarchar(128) NOT NULL, - [flag_url] nvarchar(2048) NOT NULL, - [latest_kapsarc_snapshot_id] uniqueidentifier NULL, - [is_active] bit NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_countries] PRIMARY KEY ([id]) -); - -CREATE TABLE [country_kapsarc_snapshots] ( - [id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NOT NULL, - [classification] nvarchar(64) NOT NULL, - [performance_score] decimal(5,2) NOT NULL, - [total_index] decimal(5,2) NOT NULL, - [snapshot_taken_on] datetimeoffset NOT NULL, - [source_version] nvarchar(32) NULL, - CONSTRAINT [pk_country_kapsarc_snapshots] PRIMARY KEY ([id]) -); - -CREATE TABLE [country_profiles] ( - [id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [key_initiatives_ar] nvarchar(max) NOT NULL, - [key_initiatives_en] nvarchar(max) NOT NULL, - [contact_info_ar] nvarchar(2000) NULL, - [contact_info_en] nvarchar(2000) NULL, - [last_updated_by_id] uniqueidentifier NOT NULL, - [last_updated_on] datetimeoffset NOT NULL, - [row_version] rowversion NOT NULL, - CONSTRAINT [pk_country_profiles] PRIMARY KEY ([id]) -); - -CREATE TABLE [country_resource_requests] ( - [id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NOT NULL, - [requested_by_id] uniqueidentifier NOT NULL, - [status] int NOT NULL, - [proposed_title_ar] nvarchar(512) NOT NULL, - [proposed_title_en] nvarchar(512) NOT NULL, - [proposed_description_ar] nvarchar(max) NOT NULL, - [proposed_description_en] nvarchar(max) NOT NULL, - [proposed_resource_type] int NOT NULL, - [proposed_asset_file_id] uniqueidentifier NOT NULL, - [submitted_on] datetimeoffset NOT NULL, - [admin_notes_ar] nvarchar(2000) NULL, - [admin_notes_en] nvarchar(2000) NULL, - [processed_by_id] uniqueidentifier NULL, - [processed_on] datetimeoffset NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_country_resource_requests] PRIMARY KEY ([id]) -); - -CREATE TABLE [events] ( - [id] uniqueidentifier NOT NULL, - [title_ar] nvarchar(512) NOT NULL, - [title_en] nvarchar(512) NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [starts_on] datetimeoffset NOT NULL, - [ends_on] datetimeoffset NOT NULL, - [location_ar] nvarchar(512) NULL, - [location_en] nvarchar(512) NULL, - [online_meeting_url] nvarchar(2048) NULL, - [featured_image_url] nvarchar(2048) NULL, - [i_cal_uid] nvarchar(256) NOT NULL, - [row_version] rowversion NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_events] PRIMARY KEY ([id]) -); - -CREATE TABLE [expert_profiles] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [bio_ar] nvarchar(2000) NOT NULL, - [bio_en] nvarchar(2000) NOT NULL, - [expertise_tags] nvarchar(max) NOT NULL, - [academic_title_ar] nvarchar(128) NOT NULL, - [academic_title_en] nvarchar(128) NOT NULL, - [approved_on] datetimeoffset NOT NULL, - [approved_by_id] uniqueidentifier NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_expert_profiles] PRIMARY KEY ([id]) -); - -CREATE TABLE [expert_registration_requests] ( - [id] uniqueidentifier NOT NULL, - [requested_by_id] uniqueidentifier NOT NULL, - [requested_bio_ar] nvarchar(2000) NOT NULL, - [requested_bio_en] nvarchar(2000) NOT NULL, - [requested_tags] nvarchar(max) NOT NULL, - [submitted_on] datetimeoffset NOT NULL, - [status] int NOT NULL, - [processed_by_id] uniqueidentifier NULL, - [processed_on] datetimeoffset NULL, - [rejection_reason_ar] nvarchar(1000) NULL, - [rejection_reason_en] nvarchar(1000) NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_expert_registration_requests] PRIMARY KEY ([id]) -); - -CREATE TABLE [homepage_sections] ( - [id] uniqueidentifier NOT NULL, - [section_type] int NOT NULL, - [order_index] int NOT NULL, - [content_ar] nvarchar(max) NOT NULL, - [content_en] nvarchar(max) NOT NULL, - [is_active] bit NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_homepage_sections] PRIMARY KEY ([id]) -); - -CREATE TABLE [knowledge_map_associations] ( - [id] uniqueidentifier NOT NULL, - [node_id] uniqueidentifier NOT NULL, - [associated_type] int NOT NULL, - [associated_id] uniqueidentifier NOT NULL, - [order_index] int NOT NULL, - CONSTRAINT [pk_knowledge_map_associations] PRIMARY KEY ([id]) -); - -CREATE TABLE [knowledge_map_edges] ( - [id] uniqueidentifier NOT NULL, - [map_id] uniqueidentifier NOT NULL, - [from_node_id] uniqueidentifier NOT NULL, - [to_node_id] uniqueidentifier NOT NULL, - [relationship_type] int NOT NULL, - [order_index] int NOT NULL, - CONSTRAINT [pk_knowledge_map_edges] PRIMARY KEY ([id]) -); - -CREATE TABLE [knowledge_map_nodes] ( - [id] uniqueidentifier NOT NULL, - [map_id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [node_type] int NOT NULL, - [description_ar] nvarchar(max) NULL, - [description_en] nvarchar(max) NULL, - [icon_url] nvarchar(2048) NULL, - [layout_x] float NOT NULL, - [layout_y] float NOT NULL, - [order_index] int NOT NULL, - CONSTRAINT [pk_knowledge_map_nodes] PRIMARY KEY ([id]) -); - -CREATE TABLE [knowledge_maps] ( - [id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [slug] nvarchar(128) NOT NULL, - [is_active] bit NOT NULL, - [row_version] rowversion NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_knowledge_maps] PRIMARY KEY ([id]) -); - -CREATE TABLE [news] ( - [id] uniqueidentifier NOT NULL, - [title_ar] nvarchar(512) NOT NULL, - [title_en] nvarchar(512) NOT NULL, - [content_ar] nvarchar(max) NOT NULL, - [content_en] nvarchar(max) NOT NULL, - [slug] nvarchar(256) NOT NULL, - [author_id] uniqueidentifier NOT NULL, - [featured_image_url] nvarchar(2048) NULL, - [published_on] datetimeoffset NULL, - [is_featured] bit NOT NULL, - [row_version] rowversion NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_news] PRIMARY KEY ([id]) -); - -CREATE TABLE [newsletter_subscriptions] ( - [id] uniqueidentifier NOT NULL, - [email] nvarchar(320) NOT NULL, - [locale_preference] nvarchar(2) NOT NULL, - [is_confirmed] bit NOT NULL, - [confirmation_token] nvarchar(64) NOT NULL, - [confirmed_on] datetimeoffset NULL, - [unsubscribed_on] datetimeoffset NULL, - CONSTRAINT [pk_newsletter_subscriptions] PRIMARY KEY ([id]) -); - -CREATE TABLE [notification_templates] ( - [id] uniqueidentifier NOT NULL, - [code] nvarchar(64) NOT NULL, - [subject_ar] nvarchar(512) NOT NULL, - [subject_en] nvarchar(512) NOT NULL, - [body_ar] nvarchar(max) NOT NULL, - [body_en] nvarchar(max) NOT NULL, - [channel] int NOT NULL, - [variable_schema_json] nvarchar(max) NOT NULL, - [is_active] bit NOT NULL, - CONSTRAINT [pk_notification_templates] PRIMARY KEY ([id]) -); - -CREATE TABLE [pages] ( - [id] uniqueidentifier NOT NULL, - [slug] nvarchar(256) NOT NULL, - [page_type] int NOT NULL, - [title_ar] nvarchar(512) NOT NULL, - [title_en] nvarchar(512) NOT NULL, - [content_ar] nvarchar(max) NOT NULL, - [content_en] nvarchar(max) NOT NULL, - [row_version] rowversion NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_pages] PRIMARY KEY ([id]) -); - -CREATE TABLE [post_follows] ( - [id] uniqueidentifier NOT NULL, - [post_id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [followed_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_post_follows] PRIMARY KEY ([id]) -); - -CREATE TABLE [post_ratings] ( - [id] uniqueidentifier NOT NULL, - [post_id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [stars] int NOT NULL, - [rated_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_post_ratings] PRIMARY KEY ([id]) -); - -CREATE TABLE [post_replies] ( - [id] uniqueidentifier NOT NULL, - [post_id] uniqueidentifier NOT NULL, - [author_id] uniqueidentifier NOT NULL, - [content] nvarchar(max) NOT NULL, - [locale] nvarchar(2) NOT NULL, - [parent_reply_id] uniqueidentifier NULL, - [is_by_expert] bit NOT NULL, - [created_on] datetimeoffset NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_post_replies] PRIMARY KEY ([id]) -); - -CREATE TABLE [posts] ( - [id] uniqueidentifier NOT NULL, - [topic_id] uniqueidentifier NOT NULL, - [author_id] uniqueidentifier NOT NULL, - [content] nvarchar(max) NOT NULL, - [locale] nvarchar(2) NOT NULL, - [is_answerable] bit NOT NULL, - [answered_reply_id] uniqueidentifier NULL, - [created_on] datetimeoffset NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_posts] PRIMARY KEY ([id]) -); - -CREATE TABLE [resource_categories] ( - [id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [slug] nvarchar(128) NOT NULL, - [parent_id] uniqueidentifier NULL, - [order_index] int NOT NULL, - [is_active] bit NOT NULL, - CONSTRAINT [pk_resource_categories] PRIMARY KEY ([id]) -); - -CREATE TABLE [resources] ( - [id] uniqueidentifier NOT NULL, - [title_ar] nvarchar(512) NOT NULL, - [title_en] nvarchar(512) NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [resource_type] int NOT NULL, - [category_id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NULL, - [uploaded_by_id] uniqueidentifier NOT NULL, - [asset_file_id] uniqueidentifier NOT NULL, - [published_on] datetimeoffset NULL, - [view_count] bigint NOT NULL, - [row_version] rowversion NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_resources] PRIMARY KEY ([id]) -); - -CREATE TABLE [search_query_logs] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NULL, - [query_text] nvarchar(1000) NOT NULL, - [results_count] int NOT NULL, - [response_time_ms] int NOT NULL, - [locale] nvarchar(2) NOT NULL, - [submitted_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_search_query_logs] PRIMARY KEY ([id]) -); - -CREATE TABLE [service_ratings] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NULL, - [rating] int NOT NULL, - [comment_ar] nvarchar(2000) NULL, - [comment_en] nvarchar(2000) NULL, - [page] nvarchar(256) NOT NULL, - [locale] nvarchar(2) NOT NULL, - [submitted_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_service_ratings] PRIMARY KEY ([id]) -); - -CREATE TABLE [state_representative_assignments] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NOT NULL, - [assigned_on] datetimeoffset NOT NULL, - [assigned_by_id] uniqueidentifier NOT NULL, - [revoked_on] datetimeoffset NULL, - [revoked_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_state_representative_assignments] PRIMARY KEY ([id]) -); - -CREATE TABLE [topic_follows] ( - [id] uniqueidentifier NOT NULL, - [topic_id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [followed_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_topic_follows] PRIMARY KEY ([id]) -); - -CREATE TABLE [topics] ( - [id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(256) NOT NULL, - [name_en] nvarchar(256) NOT NULL, - [description_ar] nvarchar(max) NOT NULL, - [description_en] nvarchar(max) NOT NULL, - [slug] nvarchar(128) NOT NULL, - [parent_id] uniqueidentifier NULL, - [icon_url] nvarchar(2048) NULL, - [order_index] int NOT NULL, - [is_active] bit NOT NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_topics] PRIMARY KEY ([id]) -); - -CREATE TABLE [user_follows] ( - [id] uniqueidentifier NOT NULL, - [follower_id] uniqueidentifier NOT NULL, - [followed_id] uniqueidentifier NOT NULL, - [followed_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_user_follows] PRIMARY KEY ([id]) -); - -CREATE TABLE [user_notifications] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [template_id] uniqueidentifier NOT NULL, - [rendered_subject_ar] nvarchar(512) NOT NULL, - [rendered_subject_en] nvarchar(512) NOT NULL, - [rendered_body] nvarchar(max) NOT NULL, - [rendered_locale] nvarchar(2) NOT NULL, - [channel] int NOT NULL, - [sent_on] datetimeoffset NULL, - [read_on] datetimeoffset NULL, - [status] int NOT NULL, - CONSTRAINT [pk_user_notifications] PRIMARY KEY ([id]) -); - -CREATE TABLE [AspNetRoleClaims] ( - [id] int NOT NULL IDENTITY, - [role_id] uniqueidentifier NOT NULL, - [claim_type] nvarchar(max) NULL, - [claim_value] nvarchar(max) NULL, - CONSTRAINT [pk_asp_net_role_claims] PRIMARY KEY ([id]), - CONSTRAINT [fk_asp_net_role_claims_asp_net_roles_role_id] FOREIGN KEY ([role_id]) REFERENCES [AspNetRoles] ([id]) ON DELETE CASCADE -); - -CREATE TABLE [AspNetUserClaims] ( - [id] int NOT NULL IDENTITY, - [user_id] uniqueidentifier NOT NULL, - [claim_type] nvarchar(max) NULL, - [claim_value] nvarchar(max) NULL, - CONSTRAINT [pk_asp_net_user_claims] PRIMARY KEY ([id]), - CONSTRAINT [fk_asp_net_user_claims_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE -); - -CREATE TABLE [AspNetUserLogins] ( - [login_provider] nvarchar(450) NOT NULL, - [provider_key] nvarchar(450) NOT NULL, - [provider_display_name] nvarchar(max) NULL, - [user_id] uniqueidentifier NOT NULL, - CONSTRAINT [pk_asp_net_user_logins] PRIMARY KEY ([login_provider], [provider_key]), - CONSTRAINT [fk_asp_net_user_logins_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE -); - -CREATE TABLE [AspNetUserRoles] ( - [user_id] uniqueidentifier NOT NULL, - [role_id] uniqueidentifier NOT NULL, - CONSTRAINT [pk_asp_net_user_roles] PRIMARY KEY ([user_id], [role_id]), - CONSTRAINT [fk_asp_net_user_roles_asp_net_roles_role_id] FOREIGN KEY ([role_id]) REFERENCES [AspNetRoles] ([id]) ON DELETE CASCADE, - CONSTRAINT [fk_asp_net_user_roles_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE -); - -CREATE TABLE [AspNetUserTokens] ( - [user_id] uniqueidentifier NOT NULL, - [login_provider] nvarchar(450) NOT NULL, - [name] nvarchar(450) NOT NULL, - [value] nvarchar(max) NULL, - CONSTRAINT [pk_asp_net_user_tokens] PRIMARY KEY ([user_id], [login_provider], [name]), - CONSTRAINT [fk_asp_net_user_tokens_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE -); - -CREATE INDEX [ix_asp_net_role_claims_role_id] ON [AspNetRoleClaims] ([role_id]); - -CREATE UNIQUE INDEX [RoleNameIndex] ON [AspNetRoles] ([normalized_name]) WHERE [normalized_name] IS NOT NULL; - -CREATE INDEX [ix_asp_net_user_claims_user_id] ON [AspNetUserClaims] ([user_id]); - -CREATE INDEX [ix_asp_net_user_logins_user_id] ON [AspNetUserLogins] ([user_id]); - -CREATE INDEX [ix_asp_net_user_roles_role_id] ON [AspNetUserRoles] ([role_id]); - -CREATE INDEX [EmailIndex] ON [AspNetUsers] ([normalized_email]); - -CREATE INDEX [ix_users_country_id] ON [AspNetUsers] ([country_id]); - -CREATE UNIQUE INDEX [UserNameIndex] ON [AspNetUsers] ([normalized_user_name]) WHERE [normalized_user_name] IS NOT NULL; - -CREATE INDEX [ix_asset_file_scan_status] ON [asset_files] ([virus_scan_status]); - -CREATE INDEX [ix_city_result_scenario_at] ON [city_scenario_results] ([scenario_id], [computed_at]); - -CREATE INDEX [ix_city_scenario_user_modified] ON [city_scenarios] ([user_id], [last_modified_on]); - -CREATE INDEX [ix_city_tech_is_active] ON [city_technologies] ([is_active]); - -CREATE INDEX [ix_country_iso_alpha2] ON [countries] ([iso_alpha2]); - -CREATE UNIQUE INDEX [ux_country_iso_alpha3_active] ON [countries] ([iso_alpha3]) WHERE [is_deleted] = 0; - -CREATE INDEX [ix_kapsarc_snapshot_country_taken] ON [country_kapsarc_snapshots] ([country_id], [snapshot_taken_on]); - -CREATE UNIQUE INDEX [ux_country_profile_country_id] ON [country_profiles] ([country_id]); - -CREATE INDEX [ix_country_request_country_status] ON [country_resource_requests] ([country_id], [status]); - -CREATE INDEX [ix_event_starts_on] ON [events] ([starts_on]); - -CREATE UNIQUE INDEX [ux_event_ical_uid] ON [events] ([i_cal_uid]); - -CREATE UNIQUE INDEX [ux_expert_profile_active_user] ON [expert_profiles] ([user_id]) WHERE [is_deleted] = 0; - -CREATE INDEX [ix_expert_request_requested_by] ON [expert_registration_requests] ([requested_by_id]); - -CREATE INDEX [ix_expert_request_status] ON [expert_registration_requests] ([status]); - -CREATE INDEX [ix_homepage_section_active_order] ON [homepage_sections] ([is_active], [order_index]); - -CREATE UNIQUE INDEX [ux_km_assoc_node_type_id] ON [knowledge_map_associations] ([node_id], [associated_type], [associated_id]); - -CREATE INDEX [ix_km_edge_from_node] ON [knowledge_map_edges] ([from_node_id]); - -CREATE INDEX [ix_km_edge_to_node] ON [knowledge_map_edges] ([to_node_id]); - -CREATE UNIQUE INDEX [ux_km_edge_map_from_to_relation] ON [knowledge_map_edges] ([map_id], [from_node_id], [to_node_id], [relationship_type]); - -CREATE INDEX [ix_km_node_map_order] ON [knowledge_map_nodes] ([map_id], [order_index]); - -CREATE UNIQUE INDEX [ux_knowledge_map_slug_active] ON [knowledge_maps] ([slug]) WHERE [is_deleted] = 0; - -CREATE INDEX [ix_news_published_on] ON [news] ([published_on]); - -CREATE UNIQUE INDEX [ux_news_slug_active] ON [news] ([slug]) WHERE [is_deleted] = 0; - -CREATE INDEX [ix_newsletter_token] ON [newsletter_subscriptions] ([confirmation_token]); - -CREATE UNIQUE INDEX [ux_newsletter_email] ON [newsletter_subscriptions] ([email]); - -CREATE UNIQUE INDEX [ux_notification_template_code] ON [notification_templates] ([code]); - -CREATE UNIQUE INDEX [ux_page_type_slug_active] ON [pages] ([page_type], [slug]) WHERE [is_deleted] = 0; - -CREATE UNIQUE INDEX [ux_post_follow_post_user] ON [post_follows] ([post_id], [user_id]); - -CREATE UNIQUE INDEX [ux_post_rating_post_user] ON [post_ratings] ([post_id], [user_id]); - -CREATE INDEX [ix_post_reply_parent_id] ON [post_replies] ([parent_reply_id]); - -CREATE INDEX [ix_post_reply_post_id] ON [post_replies] ([post_id]); - -CREATE INDEX [ix_post_author_created] ON [posts] ([author_id], [created_on]); - -CREATE INDEX [ix_post_topic_id] ON [posts] ([topic_id]); - -CREATE INDEX [ix_resource_category_parent_id] ON [resource_categories] ([parent_id]); - -CREATE UNIQUE INDEX [ux_resource_category_slug] ON [resource_categories] ([slug]); - -CREATE INDEX [ix_resource_asset_file_id] ON [resources] ([asset_file_id]); - -CREATE INDEX [ix_resource_category_published] ON [resources] ([category_id], [published_on]); - -CREATE INDEX [ix_resource_country_id] ON [resources] ([country_id]); - -CREATE INDEX [ix_search_query_log_submitted_on] ON [search_query_logs] ([submitted_on]); - -CREATE INDEX [ix_service_rating_submitted_on] ON [service_ratings] ([submitted_on]); - -CREATE INDEX [ix_state_rep_country_id] ON [state_representative_assignments] ([country_id]); - -CREATE INDEX [ix_state_rep_user_id] ON [state_representative_assignments] ([user_id]); - -CREATE UNIQUE INDEX [ux_state_rep_active_user_country] ON [state_representative_assignments] ([user_id], [country_id]) WHERE [is_deleted] = 0; - -CREATE UNIQUE INDEX [ux_topic_follow_topic_user] ON [topic_follows] ([topic_id], [user_id]); - -CREATE UNIQUE INDEX [ux_topic_slug_active] ON [topics] ([slug]) WHERE [is_deleted] = 0; - -CREATE UNIQUE INDEX [ux_user_follow_follower_followed] ON [user_follows] ([follower_id], [followed_id]); - -CREATE INDEX [ix_user_notification_user_status] ON [user_notifications] ([user_id], [status]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260427192344_DataDomainInitial', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -ALTER TABLE [AspNetUsers] ADD [entra_id_object_id] uniqueidentifier NULL; - -CREATE UNIQUE INDEX [ix_asp_net_users_entra_id_object_id] ON [AspNetUsers] ([entra_id_object_id]) WHERE [entra_id_object_id] IS NOT NULL; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260504182534_AddEntraIdObjectIdToUser', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -ALTER TABLE [AspNetUsers] ADD [first_name] nvarchar(50) NOT NULL DEFAULT N''; - -ALTER TABLE [AspNetUsers] ADD [job_title] nvarchar(50) NOT NULL DEFAULT N''; - -ALTER TABLE [AspNetUsers] ADD [last_name] nvarchar(50) NOT NULL DEFAULT N''; - -ALTER TABLE [AspNetUsers] ADD [organization_name] nvarchar(100) NOT NULL DEFAULT N''; - -CREATE TABLE [refresh_tokens] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [token_hash] nvarchar(128) NOT NULL, - [token_family_id] uniqueidentifier NOT NULL, - [created_at_utc] datetimeoffset NOT NULL, - [expires_at_utc] datetimeoffset NOT NULL, - [revoked_at_utc] datetimeoffset NULL, - [replaced_by_token_hash] nvarchar(128) NULL, - [created_by_ip] nvarchar(64) NULL, - [revoked_by_ip] nvarchar(64) NULL, - [user_agent] nvarchar(512) NULL, - CONSTRAINT [pk_refresh_tokens] PRIMARY KEY ([id]), - CONSTRAINT [fk_refresh_tokens_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) ON DELETE CASCADE -); - -CREATE INDEX [ix_refresh_tokens_token_family_id] ON [refresh_tokens] ([token_family_id]); - -CREATE INDEX [ix_refresh_tokens_user_id] ON [refresh_tokens] ([user_id]); - -CREATE UNIQUE INDEX [ux_refresh_tokens_token_hash] ON [refresh_tokens] ([token_hash]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260514202038_AddLocalAuthRefreshTokens', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; - - IF EXISTS ( - SELECT 1 FROM sys.columns c - JOIN sys.tables t ON c.object_id = t.object_id - WHERE t.name = 'country_profiles' AND c.name = 'last_updated_on' - ) - BEGIN - EXEC sp_rename N'[country_profiles].[last_updated_on]', N'created_on', 'COLUMN'; - END - - IF EXISTS ( - SELECT 1 FROM sys.columns c - JOIN sys.tables t ON c.object_id = t.object_id - WHERE t.name = 'country_profiles' AND c.name = 'last_updated_by_id' - ) - BEGIN - EXEC sp_rename N'[country_profiles].[last_updated_by_id]', N'created_by_id', 'COLUMN'; - END - - -ALTER TABLE [topics] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [topics] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [topics] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [topics] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [state_representative_assignments] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [state_representative_assignments] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [state_representative_assignments] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [state_representative_assignments] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [resources] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [resources] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [resources] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [resources] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [posts] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [posts] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [posts] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [post_replies] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [post_replies] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [post_replies] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [pages] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [pages] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [pages] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [pages] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [news] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [news] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [news] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [news] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [knowledge_maps] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [knowledge_maps] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [knowledge_maps] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [knowledge_maps] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [homepage_sections] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [homepage_sections] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [homepage_sections] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [homepage_sections] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [expert_registration_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [expert_registration_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [expert_registration_requests] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [expert_registration_requests] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [expert_profiles] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [expert_profiles] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [expert_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [expert_profiles] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [events] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [events] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [events] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [events] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [country_resource_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [country_resource_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [country_resource_requests] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [country_resource_requests] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [country_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [country_profiles] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [countries] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [countries] ADD [last_modified_on] datetimeoffset NULL; - -DECLARE @var nvarchar(max); -SELECT @var = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[city_scenarios]') AND [c].[name] = N'last_modified_on'); -IF @var IS NOT NULL EXEC(N'ALTER TABLE [city_scenarios] DROP CONSTRAINT ' + @var + ';'); -ALTER TABLE [city_scenarios] ALTER COLUMN [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [city_scenarios] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [city_scenarios] ADD [last_modified_by_id] uniqueidentifier NULL; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260515121258_StandardizeCountryProfileAudit', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -EXEC sp_rename N'[country_profiles].[last_updated_on]', N'created_on', 'COLUMN'; - -EXEC sp_rename N'[country_profiles].[last_updated_by_id]', N'created_by_id', 'COLUMN'; - -ALTER TABLE [topics] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [topics] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [topics] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [topics] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [state_representative_assignments] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [state_representative_assignments] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [state_representative_assignments] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [state_representative_assignments] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [resources] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [resources] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [resources] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [resources] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [posts] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [posts] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [posts] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [post_replies] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [post_replies] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [post_replies] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [pages] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [pages] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [pages] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [pages] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [newsletter_subscriptions] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [newsletter_subscriptions] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [newsletter_subscriptions] ADD [deleted_by_id] uniqueidentifier NULL; - -ALTER TABLE [newsletter_subscriptions] ADD [deleted_on] datetimeoffset NULL; - -ALTER TABLE [newsletter_subscriptions] ADD [is_deleted] bit NOT NULL DEFAULT CAST(0 AS bit); - -ALTER TABLE [newsletter_subscriptions] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [newsletter_subscriptions] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [news] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [news] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [news] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [news] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [knowledge_maps] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [knowledge_maps] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [knowledge_maps] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [knowledge_maps] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [homepage_sections] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [homepage_sections] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [homepage_sections] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [homepage_sections] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [expert_registration_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [expert_registration_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [expert_registration_requests] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [expert_registration_requests] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [expert_profiles] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [expert_profiles] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [expert_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [expert_profiles] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [events] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [events] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [events] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [events] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [country_resource_requests] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [country_resource_requests] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [country_resource_requests] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [country_resource_requests] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [country_profiles] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [country_profiles] ADD [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [countries] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [countries] ADD [last_modified_on] datetimeoffset NULL; - -DECLARE @var1 nvarchar(max); -SELECT @var1 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[city_scenarios]') AND [c].[name] = N'last_modified_on'); -IF @var1 IS NOT NULL EXEC(N'ALTER TABLE [city_scenarios] DROP CONSTRAINT ' + @var1 + ';'); -ALTER TABLE [city_scenarios] ALTER COLUMN [last_modified_on] datetimeoffset NULL; - -ALTER TABLE [city_scenarios] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [city_scenarios] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [AspNetUsers] ADD [status] int NOT NULL DEFAULT 0; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260520101638_AddUserStatus', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -ALTER TABLE [AspNetUsers] ADD [deleted_by_id] uniqueidentifier NULL; - -ALTER TABLE [AspNetUsers] ADD [deleted_on] datetimeoffset NULL; - -ALTER TABLE [AspNetUsers] ADD [is_deleted] bit NOT NULL DEFAULT CAST(0 AS bit); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260520111756_AddUserSoftDelete', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -CREATE TABLE [about_settings] ( - [id] uniqueidentifier NOT NULL, - [description_ar] nvarchar(1000) NOT NULL, - [description_en] nvarchar(1000) NOT NULL, - [how_to_use_video_url] nvarchar(max) NULL, - [row_version] rowversion NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_about_settings] PRIMARY KEY ([id]) -); - -CREATE TABLE [glossary_entries] ( - [id] uniqueidentifier NOT NULL, - [about_settings_id] uniqueidentifier NOT NULL, - [term_ar] nvarchar(100) NOT NULL, - [term_en] nvarchar(100) NOT NULL, - [definition_ar] nvarchar(1000) NOT NULL, - [definition_en] nvarchar(1000) NOT NULL, - [order_index] int NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_glossary_entries] PRIMARY KEY ([id]) -); - -CREATE TABLE [homepage_countries] ( - [id] uniqueidentifier NOT NULL, - [homepage_settings_id] uniqueidentifier NOT NULL, - [country_id] uniqueidentifier NOT NULL, - [order_index] int NOT NULL, - CONSTRAINT [pk_homepage_countries] PRIMARY KEY ([id]) -); - -CREATE TABLE [homepage_settings] ( - [id] uniqueidentifier NOT NULL, - [video_url] nvarchar(max) NULL, - [objective_ar] nvarchar(1000) NOT NULL, - [objective_en] nvarchar(1000) NOT NULL, - [cce_concepts_ar] nvarchar(max) NOT NULL, - [cce_concepts_en] nvarchar(max) NOT NULL, - [row_version] rowversion NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_homepage_settings] PRIMARY KEY ([id]) -); - -CREATE TABLE [knowledge_partners] ( - [id] uniqueidentifier NOT NULL, - [about_settings_id] uniqueidentifier NOT NULL, - [name_ar] nvarchar(200) NOT NULL, - [name_en] nvarchar(200) NOT NULL, - [logo_url] nvarchar(max) NULL, - [website_url] nvarchar(max) NULL, - [description_ar] nvarchar(1000) NULL, - [description_en] nvarchar(1000) NULL, - [order_index] int NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_knowledge_partners] PRIMARY KEY ([id]) -); - -CREATE TABLE [policies_settings] ( - [id] uniqueidentifier NOT NULL, - [row_version] rowversion NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_policies_settings] PRIMARY KEY ([id]) -); - -CREATE TABLE [policy_sections] ( - [id] uniqueidentifier NOT NULL, - [policies_settings_id] uniqueidentifier NOT NULL, - [type] int NOT NULL, - [title_ar] nvarchar(500) NOT NULL, - [title_en] nvarchar(500) NOT NULL, - [content_ar] nvarchar(max) NOT NULL, - [content_en] nvarchar(max) NOT NULL, - [order_index] int NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_policy_sections] PRIMARY KEY ([id]) -); - -CREATE UNIQUE INDEX [ix_homepage_country_settings_country] ON [homepage_countries] ([homepage_settings_id], [country_id]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260521094531_AddPlatformSettings', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -CREATE TABLE [media_files] ( - [id] uniqueidentifier NOT NULL, - [storage_key] nvarchar(500) NOT NULL, - [url] nvarchar(2048) NOT NULL, - [original_file_name] nvarchar(255) NOT NULL, - [mime_type] nvarchar(100) NOT NULL, - [size_bytes] bigint NOT NULL, - [title_ar] nvarchar(200) NULL, - [title_en] nvarchar(200) NULL, - [description_ar] nvarchar(1000) NULL, - [description_en] nvarchar(1000) NULL, - [alt_text_ar] nvarchar(500) NULL, - [alt_text_en] nvarchar(500) NULL, - [uploaded_by_id] uniqueidentifier NOT NULL, - [uploaded_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_media_files] PRIMARY KEY ([id]) -); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260521111720_AddMediaService', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -DECLARE @var2 nvarchar(max); -SELECT @var2 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'deleted_by_id'); -IF @var2 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var2 + ';'); -ALTER TABLE [policy_sections] DROP COLUMN [deleted_by_id]; - -DECLARE @var3 nvarchar(max); -SELECT @var3 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'deleted_on'); -IF @var3 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var3 + ';'); -ALTER TABLE [policy_sections] DROP COLUMN [deleted_on]; - -DECLARE @var4 nvarchar(max); -SELECT @var4 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[policy_sections]') AND [c].[name] = N'is_deleted'); -IF @var4 IS NOT NULL EXEC(N'ALTER TABLE [policy_sections] DROP CONSTRAINT ' + @var4 + ';'); -ALTER TABLE [policy_sections] DROP COLUMN [is_deleted]; - -DECLARE @var5 nvarchar(max); -SELECT @var5 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'deleted_by_id'); -IF @var5 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var5 + ';'); -ALTER TABLE [knowledge_partners] DROP COLUMN [deleted_by_id]; - -DECLARE @var6 nvarchar(max); -SELECT @var6 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'deleted_on'); -IF @var6 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var6 + ';'); -ALTER TABLE [knowledge_partners] DROP COLUMN [deleted_on]; - -DECLARE @var7 nvarchar(max); -SELECT @var7 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[knowledge_partners]') AND [c].[name] = N'is_deleted'); -IF @var7 IS NOT NULL EXEC(N'ALTER TABLE [knowledge_partners] DROP CONSTRAINT ' + @var7 + ';'); -ALTER TABLE [knowledge_partners] DROP COLUMN [is_deleted]; - -DECLARE @var8 nvarchar(max); -SELECT @var8 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'deleted_by_id'); -IF @var8 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var8 + ';'); -ALTER TABLE [glossary_entries] DROP COLUMN [deleted_by_id]; - -DECLARE @var9 nvarchar(max); -SELECT @var9 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'deleted_on'); -IF @var9 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var9 + ';'); -ALTER TABLE [glossary_entries] DROP COLUMN [deleted_on]; - -DECLARE @var10 nvarchar(max); -SELECT @var10 = QUOTENAME([d].[name]) -FROM [sys].[default_constraints] [d] -INNER JOIN [sys].[columns] [c] ON [d].[parent_column_id] = [c].[column_id] AND [d].[parent_object_id] = [c].[object_id] -WHERE ([d].[parent_object_id] = OBJECT_ID(N'[glossary_entries]') AND [c].[name] = N'is_deleted'); -IF @var10 IS NOT NULL EXEC(N'ALTER TABLE [glossary_entries] DROP CONSTRAINT ' + @var10 + ';'); -ALTER TABLE [glossary_entries] DROP COLUMN [is_deleted]; - -ALTER TABLE [homepage_countries] ADD [created_by_id] uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'; - -ALTER TABLE [homepage_countries] ADD [created_on] datetimeoffset NOT NULL DEFAULT '0001-01-01T00:00:00.0000000+00:00'; - -ALTER TABLE [homepage_countries] ADD [last_modified_by_id] uniqueidentifier NULL; - -ALTER TABLE [homepage_countries] ADD [last_modified_on] datetimeoffset NULL; - -CREATE INDEX [ix_policy_sections_policies_settings_id] ON [policy_sections] ([policies_settings_id]); - -CREATE INDEX [ix_knowledge_partners_about_settings_id] ON [knowledge_partners] ([about_settings_id]); - -CREATE INDEX [ix_glossary_entries_about_settings_id] ON [glossary_entries] ([about_settings_id]); - -ALTER TABLE [glossary_entries] ADD CONSTRAINT [fk_glossary_entries_about_settings_about_settings_id] FOREIGN KEY ([about_settings_id]) REFERENCES [about_settings] ([id]) ON DELETE CASCADE; - -ALTER TABLE [homepage_countries] ADD CONSTRAINT [fk_homepage_countries_homepage_settings_homepage_settings_id] FOREIGN KEY ([homepage_settings_id]) REFERENCES [homepage_settings] ([id]) ON DELETE CASCADE; - -ALTER TABLE [knowledge_partners] ADD CONSTRAINT [fk_knowledge_partners_about_settings_about_settings_id] FOREIGN KEY ([about_settings_id]) REFERENCES [about_settings] ([id]) ON DELETE CASCADE; - -ALTER TABLE [policy_sections] ADD CONSTRAINT [fk_policy_sections_policies_settings_policies_settings_id] FOREIGN KEY ([policies_settings_id]) REFERENCES [policies_settings] ([id]) ON DELETE CASCADE; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260522211302_RefactorPlatformSettings', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -DROP INDEX [ux_notification_template_code] ON [notification_templates]; - -CREATE TABLE [notification_logs] ( - [id] uniqueidentifier NOT NULL, - [recipient_user_id] uniqueidentifier NULL, - [template_code] nvarchar(64) NOT NULL, - [template_id] uniqueidentifier NULL, - [channel] int NOT NULL, - [status] int NOT NULL, - [provider_message_id] nvarchar(256) NULL, - [error] nvarchar(max) NULL, - [attempt_count] int NOT NULL, - [created_on] datetimeoffset NOT NULL, - [sent_on] datetimeoffset NULL, - [failed_on] datetimeoffset NULL, - [correlation_id] nvarchar(64) NULL, - [payload_json] nvarchar(max) NULL, - CONSTRAINT [pk_notification_logs] PRIMARY KEY ([id]) -); - -CREATE TABLE [user_notification_settings] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NOT NULL, - [channel] int NOT NULL, - [event_code] nvarchar(64) NULL, - [is_enabled] bit NOT NULL, - [updated_on] datetimeoffset NOT NULL, - CONSTRAINT [pk_user_notification_settings] PRIMARY KEY ([id]) -); - -CREATE UNIQUE INDEX [ux_notification_template_code_channel] ON [notification_templates] ([code], [channel]); - -CREATE INDEX [ix_notification_log_correlation_id] ON [notification_logs] ([correlation_id]); - -CREATE INDEX [ix_notification_log_recipient_status_created] ON [notification_logs] ([recipient_user_id], [status], [created_on]); - -CREATE INDEX [ix_notification_log_template_channel] ON [notification_logs] ([template_code], [channel]); - -CREATE UNIQUE INDEX [ux_user_notification_settings_user_channel_event] ON [user_notification_settings] ([user_id], [channel], [event_code]) WHERE [event_code] IS NOT NULL; - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260523111750_AddNotificationGateway', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -CREATE TABLE [otp_verifications] ( - [id] uniqueidentifier NOT NULL, - [contact] nvarchar(256) NOT NULL, - [type_id] int NOT NULL, - [code_hash] nvarchar(512) NOT NULL, - [expires_at] datetimeoffset NOT NULL, - [created_at] datetimeoffset NOT NULL, - [last_sent_at] datetimeoffset NULL, - [attempt_count] int NOT NULL, - [is_verified] bit NOT NULL, - [is_invalidated] bit NOT NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_otp_verifications] PRIMARY KEY ([id]) -); - -CREATE TABLE [user_verifications] ( - [id] uniqueidentifier NOT NULL, - [user_id] uniqueidentifier NULL, - [contact] nvarchar(256) NOT NULL, - [type_id] int NOT NULL, - [is_verified] bit NOT NULL, - [verified_at] datetimeoffset NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - [is_deleted] bit NOT NULL, - [deleted_on] datetimeoffset NULL, - [deleted_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_user_verifications] PRIMARY KEY ([id]), - CONSTRAINT [fk_user_verifications_asp_net_users_user_id] FOREIGN KEY ([user_id]) REFERENCES [AspNetUsers] ([id]) -); - -CREATE INDEX [ix_otp_verifications_contact_type_id] ON [otp_verifications] ([contact], [type_id]); - -CREATE UNIQUE INDEX [ix_user_verifications_contact_type_id] ON [user_verifications] ([contact], [type_id]); - -CREATE INDEX [ix_user_verifications_user_id] ON [user_verifications] ([user_id]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260523180351_AddOtpVerification', N'10.0.1'); - -COMMIT; -GO - -BEGIN TRANSACTION; -CREATE TABLE [service_evaluations] ( - [id] uniqueidentifier NOT NULL, - [overall_satisfaction] int NOT NULL, - [ease_of_use] int NOT NULL, - [content_suitability] int NOT NULL, - [feedback] nvarchar(500) NOT NULL, - [user_id] uniqueidentifier NULL, - [created_on] datetimeoffset NOT NULL, - [created_by_id] uniqueidentifier NOT NULL, - [last_modified_on] datetimeoffset NULL, - [last_modified_by_id] uniqueidentifier NULL, - CONSTRAINT [pk_service_evaluations] PRIMARY KEY ([id]) -); - -CREATE INDEX [ix_service_evaluation_created_on] ON [service_evaluations] ([created_on]); - -INSERT INTO [__EFMigrationsHistory] ([migration_id], [product_version]) -VALUES (N'20260525092623_AddServiceEvaluation', N'10.0.1'); - -COMMIT; -GO - From 46c016087cb35681790457d82cf4e659e4df34a9 Mon Sep 17 00:00:00 2001 From: ahmed Date: Wed, 27 May 2026 20:02:33 +0300 Subject: [PATCH 4/5] feat: complete US018 service evaluation with project pattern alignment --- .../Endpoints/EvaluationEndpoints.cs | 10 +-------- .../Endpoints/EvaluationEndpoints.cs | 3 ++- .../SubmitEvaluationCommandHandler.cs | 8 +++++-- .../SubmitEvaluationCommandValidator.cs | 3 --- .../Evaluation/DTOs/ServiceEvaluationDto.cs | 2 +- .../GetAllEvaluationsQuery.cs | 6 +++++- .../GetAllEvaluationsQueryHandler.cs | 21 ++++++++++--------- .../src/CCE.Domain/Common/AuditableEntity.cs | 2 +- .../Evaluation/ServiceEvaluation.cs | 3 +-- .../Evaluation/EvaluationRepository.cs | 1 - .../20260525092623_AddServiceEvaluation.cs | 4 ++-- 11 files changed, 30 insertions(+), 33 deletions(-) diff --git a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs index 5823b9dd..e50670f6 100644 --- a/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs +++ b/backend/src/CCE.Api.External/Endpoints/EvaluationEndpoints.cs @@ -20,15 +20,7 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu IMediator mediator, CancellationToken ct) => { - if (!Enum.IsDefined(typeof(EvaluationRating), body.OverallSatisfaction) || body.OverallSatisfaction == 0) - return Results.BadRequest(new { error = "OverallSatisfaction must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); - - if (!Enum.IsDefined(typeof(EvaluationRating), body.EaseOfUse) || body.EaseOfUse == 0) - return Results.BadRequest(new { error = "EaseOfUse must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); - - if (!Enum.IsDefined(typeof(EvaluationRating), body.ContentSuitability) || body.ContentSuitability == 0) - return Results.BadRequest(new { error = "ContentSuitability must be 1-5 (1=Excellent, 2=Satisfied, 3=Neutral, 4=Dissatisfied, 5=Poor)." }); - + var cmd = new SubmitEvaluationCommand( (EvaluationRating)body.OverallSatisfaction, (EvaluationRating)body.EaseOfUse, diff --git a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs index 9db5151b..57420849 100644 --- a/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs +++ b/backend/src/CCE.Api.Internal/Endpoints/EvaluationEndpoints.cs @@ -17,10 +17,11 @@ public static IEndpointRouteBuilder MapEvaluationEndpoints(this IEndpointRouteBu // GET /api/admin/evaluations — list all (admin only) group.MapGet("", async ( + int? page, int? pageSize, IMediator mediator, CancellationToken ct) => { - var result = await mediator.Send(new GetAllEvaluationsQuery(), ct).ConfigureAwait(false); + var result = await mediator.Send(new GetAllEvaluationsQuery(page ?? 1, pageSize ?? 20), ct).ConfigureAwait(false); return result.ToHttpResult(); }) .RequireAuthorization(Permissions.Survey_ReadAll) diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs index e517f7b2..9e1bc05f 100644 --- a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandHandler.cs @@ -15,17 +15,20 @@ public sealed class SubmitEvaluationCommandHandler private readonly ICurrentUserAccessor _currentUser; private readonly ISystemClock _clock; private readonly MessageFactory _messageFactory; + private readonly ICceDbContext _db; public SubmitEvaluationCommandHandler( IEvaluationRepository repository, ICurrentUserAccessor currentUser, ISystemClock clock, - MessageFactory messageFactory) + MessageFactory messageFactory, + ICceDbContext db) { _repository = repository; _currentUser = currentUser; _clock = clock; _messageFactory = messageFactory; + _db = db; } public async Task> Handle( @@ -43,7 +46,8 @@ public async Task> Handle( _clock); await _repository.AddAsync(evaluation, cancellationToken).ConfigureAwait(false); - + await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); + return _messageFactory.Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED); } } diff --git a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs index 395629f0..9ba6c291 100644 --- a/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs +++ b/backend/src/CCE.Application/Evaluation/Commands/SubmitEvaluation/SubmitEvaluationCommandValidator.cs @@ -8,13 +8,10 @@ public sealed class SubmitEvaluationCommandValidator : AbstractValidator x.OverallSatisfaction) - .IsInEnum().WithErrorCode("INVALID_ENUM") .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); RuleFor(x => x.EaseOfUse) - .IsInEnum().WithErrorCode("INVALID_ENUM") .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); RuleFor(x => x.ContentSuitability) - .IsInEnum().WithErrorCode("INVALID_ENUM") .NotEqual(EvaluationRating.None).WithErrorCode("REQUIRED_FIELD"); RuleFor(x => x.Feedback) .NotEmpty().WithErrorCode("REQUIRED_FIELD") diff --git a/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs index 962551ed..ae99acad 100644 --- a/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs +++ b/backend/src/CCE.Application/Evaluation/DTOs/ServiceEvaluationDto.cs @@ -10,4 +10,4 @@ public sealed record ServiceEvaluationDto( string Feedback, System.Guid? UserId, System.DateTimeOffset CreatedOn, - System.Guid CreatedById); + System.Guid? CreatedById); diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs index 22c3e04b..9186de64 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQuery.cs @@ -1,7 +1,11 @@ using CCE.Application.Common; +using CCE.Application.Common.Pagination; using CCE.Application.Evaluation.DTOs; using MediatR; namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; -public sealed record GetAllEvaluationsQuery : IRequest>>; +public sealed record GetAllEvaluationsQuery( + int Page = 1, + int PageSize = 20) + : IRequest>>; diff --git a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs index 6544d843..f8b93aea 100644 --- a/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs +++ b/backend/src/CCE.Application/Evaluation/Queries/GetAllEvaluations/GetAllEvaluationsQueryHandler.cs @@ -4,11 +4,13 @@ using CCE.Application.Messages; using MediatR; using Microsoft.EntityFrameworkCore; +using CCE.Application.Common.Pagination; namespace CCE.Application.Evaluation.Queries.GetAllEvaluations; public sealed class GetAllEvaluationsQueryHandler - : IRequestHandler>> + : IRequestHandler>> { private readonly ICceDbContext _db; private readonly MessageFactory _msg; @@ -19,16 +21,16 @@ public GetAllEvaluationsQueryHandler(ICceDbContext db, MessageFactory msg) _msg = msg; } - public async Task>> Handle( + public async Task>> Handle( GetAllEvaluationsQuery request, CancellationToken cancellationToken) { - var evaluations = await _db.ServiceEvaluations - .OrderByDescending(e => e.CreatedOn) - .ToListAsync(cancellationToken) + var query = _db.ServiceEvaluations + .OrderByDescending(e => e.CreatedOn); + var page = await query.ToPagedResultAsync( + request.Page, request.PageSize, cancellationToken) .ConfigureAwait(false); - - var dtos = evaluations.Select(e => new ServiceEvaluationDto( + var result = page.Map(e => new ServiceEvaluationDto( e.Id, e.OverallSatisfaction, e.EaseOfUse, @@ -36,8 +38,7 @@ public async Task>> Handle( e.Feedback, e.UserId, e.CreatedOn, - e.CreatedById)).ToList(); - - return _msg.Ok(dtos, "ITEMS_LISTED"); + e.CreatedById)); + return _msg.Ok(result, "ITEMS_LISTED"); } } diff --git a/backend/src/CCE.Domain/Common/AuditableEntity.cs b/backend/src/CCE.Domain/Common/AuditableEntity.cs index a1ab1f0c..cc355a1e 100644 --- a/backend/src/CCE.Domain/Common/AuditableEntity.cs +++ b/backend/src/CCE.Domain/Common/AuditableEntity.cs @@ -15,7 +15,7 @@ protected AuditableEntity(TId id) : base(id) { } public DateTimeOffset CreatedOn { get; protected set; } /// - public Guid CreatedById { get; protected set; } + public Guid? CreatedById { get; protected set; } /// public DateTimeOffset? LastModifiedOn { get; protected set; } diff --git a/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs index e443cf51..bb981eac 100644 --- a/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs +++ b/backend/src/CCE.Domain/Evaluation/ServiceEvaluation.cs @@ -5,7 +5,6 @@ namespace CCE.Domain.Evaluation; [Audited] public sealed class ServiceEvaluation : AuditableEntity { - private static readonly System.Guid AnonymousVisitorId = new("00000000-0000-0000-0000-000000000001"); private ServiceEvaluation( System.Guid id, @@ -54,7 +53,7 @@ public static ServiceEvaluation Submit( userId); entity.CreatedOn = clock.UtcNow; - entity.CreatedById = userId ?? AnonymousVisitorId; + entity.CreatedById = userId; return entity; } diff --git a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs index b8900efc..429f2be8 100644 --- a/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs +++ b/backend/src/CCE.Infrastructure/Evaluation/EvaluationRepository.cs @@ -16,6 +16,5 @@ public EvaluationRepository(CceDbContext db) public async Task AddAsync(ServiceEvaluation evaluation, CancellationToken ct) { _db.ServiceEvaluations.Add(evaluation); - await _db.SaveChangesAsync(ct).ConfigureAwait(false); } } diff --git a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs index 2ab9c12b..658203b3 100644 --- a/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs +++ b/backend/src/CCE.Infrastructure/Persistence/Migrations/20260525092623_AddServiceEvaluation.cs @@ -15,14 +15,14 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "service_evaluations", columns: table => new { - id = table.Column(type: "uniqueidentifier", nullable: false), + id = table.Column(type: "uniqueidentifier", nullable: true), overall_satisfaction = table.Column(type: "int", nullable: false), ease_of_use = table.Column(type: "int", nullable: false), content_suitability = table.Column(type: "int", nullable: false), feedback = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), user_id = table.Column(type: "uniqueidentifier", nullable: true), created_on = table.Column(type: "datetimeoffset", nullable: false), - created_by_id = table.Column(type: "uniqueidentifier", nullable: false), + created_by_id = table.Column(type: "uniqueidentifier", nullable: true), last_modified_on = table.Column(type: "datetimeoffset", nullable: true), last_modified_by_id = table.Column(type: "uniqueidentifier", nullable: true) }, From c72f16c1cd4d961491ef053166e31db459670a4c Mon Sep 17 00:00:00 2001 From: ahmed Date: Thu, 28 May 2026 18:00:31 +0300 Subject: [PATCH 5/5] fix: resolve merge conflict in Program.cs --- backend/src/CCE.Api.Internal/Program.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/backend/src/CCE.Api.Internal/Program.cs b/backend/src/CCE.Api.Internal/Program.cs index 5247d0a0..1c34eca6 100644 --- a/backend/src/CCE.Api.Internal/Program.cs +++ b/backend/src/CCE.Api.Internal/Program.cs @@ -85,13 +85,9 @@ app.MapHomepageSettingsEndpoints(); app.MapAboutSettingsEndpoints(); app.MapPoliciesSettingsEndpoints(); -<<<<<<< feat/services-evaluation -app.MapMediaEndpoints(); -app.MapEvaluationEndpoints(); -======= app.MapMediaEndpoints(); app.MapCountryCodeEndpoints(); ->>>>>>> develop + app.MapEvaluationEndpoints(); // Sub-11d follow-up — dev sign-in shim. Mounts /dev/sign-in, // /dev/sign-out, /dev/whoami when Auth:DevMode=true. Production