diff --git a/backend/src/CCE.Api.Common/Localization/Resources.yaml b/backend/src/CCE.Api.Common/Localization/Resources.yaml
index a5ce2845..621505f5 100644
--- a/backend/src/CCE.Api.Common/Localization/Resources.yaml
+++ b/backend/src/CCE.Api.Common/Localization/Resources.yaml
@@ -1,195 +1,11 @@
-GENERAL_VALIDATION_ERROR:
- ar: "عذرًا، البيانات المدخلة غير صحيحة"
- en: "Sorry, the entered data is invalid"
-
-GENERAL_INTERNAL_ERROR:
- ar: "حدث خطأ غير متوقع"
- en: "An unexpected error occurred"
-
-GENERAL_UNAUTHORIZED:
- ar: "الوصول غير مصرح به"
- en: "Unauthorized access"
-
-GENERAL_FORBIDDEN:
- ar: "الوصول ممنوع"
- en: "Forbidden access"
-
-GENERAL_NOT_FOUND:
- ar: "المورد غير موجود"
- en: "Resource not found"
-
-GENERAL_BAD_REQUEST:
- ar: "عذرًا، البيانات المدخلة غير صحيحة"
- en: "Sorry, the entered data is invalid"
-
-GENERAL_SUCCESS_CREATED:
- ar: "تم الإنشاء بنجاح"
- en: "Created successfully"
-
-GENERAL_SUCCESS_UPDATED:
- ar: "تم التحديث بنجاح"
- en: "Updated successfully"
-
-GENERAL_SUCCESS_DELETED:
- ar: "تم الحذف بنجاح"
- en: "Deleted successfully"
-
-GENERAL_SUCCESS_OPERATION:
- ar: "تمت العملية بنجاح"
- en: "Operation completed successfully"
-
-IDENTITY_USER_NOT_FOUND:
- ar: "عذرًا، لم يتم العثور على المستخدم"
- en: "Sorry, user not found"
-
-IDENTITY_EMAIL_EXISTS:
- ar: "عذرًا، حدثت مشكلة أثناء إنشاء الحساب"
- en: "Sorry, a problem occurred while creating the account"
-
-IDENTITY_USERNAME_EXISTS:
- ar: "اسم المستخدم مستخدم بالفعل"
- en: "Username already taken"
-
-IDENTITY_INVALID_CREDENTIALS:
- ar: "البريد الإلكتروني أو كلمة المرور غير صحيحة"
- en: "Invalid email or password"
-
-IDENTITY_NOT_AUTHENTICATED:
- ar: "المستخدم غير مصادق"
- en: "User not authenticated"
-
-IDENTITY_EXPERT_REQUEST_NOT_FOUND:
- ar: "طلب الخبير غير موجود"
- en: "Expert request not found"
-
-IDENTITY_STATE_REP_ASSIGNMENT_NOT_FOUND:
- ar: "التعيين غير موجود"
- en: "Assignment not found"
-
-IDENTITY_STATE_REP_ASSIGNMENT_EXISTS:
- ar: "التعيين موجود بالفعل"
- en: "Assignment already exists"
-
-IDENTITY_INVALID_TOKEN:
- ar: "رمز الوصول غير صالح"
- en: "Invalid access token"
-
-IDENTITY_ACCOUNT_DEACTIVATED:
- ar: "الحساب غير نشط"
- en: "Account is deactivated"
-
-CONTENT_RESOURCE_NOT_FOUND:
- ar: "المورد غير موجود"
- en: "Resource not found"
-
-CONTENT_RESOURCE_DUPLICATE:
- ar: "المورد موجود بالفعل"
- en: "Resource already exists"
-
-CONTENT_CATEGORY_NOT_FOUND:
- ar: "التصنيف غير موجود"
- en: "Category not found"
-
-CONTENT_CATEGORY_DUPLICATE:
- ar: "التصنيف موجود بالفعل"
- en: "Category already exists"
-
-CONTENT_PAGE_NOT_FOUND:
- ar: "الصفحة غير موجودة"
- en: "Page not found"
-
-CONTENT_NEWS_NOT_FOUND:
- ar: "الخبر غير موجود"
- en: "News not found"
-
-CONTENT_EVENT_NOT_FOUND:
- ar: "الفعالية غير موجودة"
- en: "Event not found"
-
-CONTENT_ASSET_NOT_FOUND:
- ar: "الملف غير موجود"
- en: "Asset not found"
-
-COMMUNITY_TOPIC_NOT_FOUND:
- ar: "الموضوع غير موجود"
- en: "Topic not found"
-
-COMMUNITY_TOPIC_DUPLICATE:
- ar: "الموضوع موجود بالفعل"
- en: "Topic already exists"
-
-COMMUNITY_POST_NOT_FOUND:
- ar: "المنشور غير موجود"
- en: "Post not found"
-
-COMMUNITY_REPLY_NOT_FOUND:
- ar: "الرد غير موجود"
- en: "Reply not found"
-
-COMMUNITY_ALREADY_FOLLOWING:
- ar: "أنت تتابع هذا بالفعل"
- en: "You are already following this"
-
-COMMUNITY_NOT_FOLLOWING:
- ar: "أنت لا تتابع هذا"
- en: "You are not following this"
-
-COMMUNITY_CANNOT_MARK_ANSWERED:
- ar: "غير مصرح لك بتحديد الإجابة"
- en: "You are not authorized to mark the answer"
-
-COMMUNITY_EDIT_WINDOW_EXPIRED:
- ar: "انتهت فترة التعديل"
- en: "Edit window has expired"
-
-COUNTRY_COUNTRY_NOT_FOUND:
- ar: "الدولة غير موجودة"
- en: "Country not found"
-
-COUNTRY_COUNTRY_PROFILE_NOT_FOUND:
- ar: "الملف التعريفي غير موجود"
- en: "Country profile not found"
-
-NOTIFICATIONS_TEMPLATE_NOT_FOUND:
- ar: "القالب غير موجود"
- en: "Template not found"
-
-NOTIFICATIONS_NOTIFICATION_NOT_FOUND:
- ar: "الإشعار غير موجود"
- en: "Notification not found"
-
-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"
-
-VALIDATION_MIN_LENGTH:
- ar: "القيمة قصيرة جدًا"
- en: "Value is too short"
-
-VALIDATION_MAX_LENGTH:
- ar: "القيمة طويلة جدًا"
- en: "Value is too long"
-
MAX_LENGTH:
ar: "القيمة طويلة جدًا"
en: "Value is too long"
-VALIDATION_INVALID_FORMAT:
- ar: "التنسيق غير صالح"
- en: "Invalid format"
-
-VALIDATION_INVALID_ENUM:
- ar: "القيمة المحددة غير صالحة"
- en: "Selected value is invalid"
-
INVALID_ENUM:
ar: "القيمة المحددة غير صالحة"
en: "Selected value is invalid"
@@ -324,34 +140,6 @@ DUPLICATE_VALUE:
ar: "القيمة موجودة بالفعل"
en: "Value already exists"
-CONTENT_HOMEPAGE_SECTION_NOT_FOUND:
- ar: "القسم غير موجود"
- en: "Section not found"
-
-CONTENT_PAGE_DUPLICATE:
- ar: "الصفحة موجودة بالفعل"
- en: "Page already exists"
-
-CONTENT_COUNTRY_RESOURCE_REQUEST_NOT_FOUND:
- ar: "طلب المورد غير موجود"
- en: "Resource request not found"
-
-IDENTITY_EXPERT_REQUEST_ALREADY_EXISTS:
- ar: "طلب الخبير موجود بالفعل"
- en: "Expert request already exists"
-
-KNOWLEDGE_MAP_NOT_FOUND:
- ar: "خريطة المعرفة غير موجودة"
- en: "Knowledge map not found"
-
-KNOWLEDGE_NODE_NOT_FOUND:
- ar: "العقدة غير موجودة"
- en: "Node not found"
-
-KNOWLEDGE_EDGE_NOT_FOUND:
- ar: "الوصلة غير موجودة"
- en: "Edge not found"
-
SCENARIO_NOT_FOUND:
ar: "السيناريو غير موجود"
en: "Scenario not found"
@@ -522,3 +310,291 @@ RESOURCE_SHARE_SUCCESS:
RESOURCE_SHARE_FAILED:
ar: "حدث خطأ أثناء محاولة مشاركة المصدر. يرجى المحاولة مرة أخرى لاحقاً."
en: "An error occurred while trying to share the resource. Please try again later."
+
+# ─── Identity Errors (bare keys without IDENTITY_ prefix) ───
+
+INVALID_TOKEN:
+ ar: "رمز الوصول غير صالح"
+ en: "Invalid access token"
+
+ACCOUNT_DEACTIVATED:
+ ar: "الحساب غير نشط"
+ en: "Account is deactivated"
+
+USERNAME_EXISTS:
+ ar: "اسم المستخدم مستخدم بالفعل"
+ en: "Username already taken"
+
+EXPERT_REQUEST_ALREADY_EXISTS:
+ ar: "طلب الخبير موجود بالفعل"
+ en: "Expert request already exists"
+
+STATE_REP_ASSIGNMENT_EXISTS:
+ ar: "التعيين موجود بالفعل"
+ en: "Assignment already exists"
+
+PASSWORD_RECOVERY_FAILED:
+ ar: "حدث خطأ أثناء استعادة كلمة المرور"
+ en: "An error occurred during password recovery"
+
+LOGOUT_FAILED:
+ ar: "حدث خطأ أثناء تسجيل الخروج"
+ en: "An error occurred during logout"
+
+UNAUTHORIZED_ACCESS:
+ ar: "الوصول غير مصرح به"
+ en: "Unauthorized access"
+
+FORBIDDEN_ACCESS:
+ ar: "الوصول ممنوع"
+ en: "Forbidden access"
+
+# ─── Content Errors (bare keys without CONTENT_ prefix) ───
+
+NEWS_NOT_FOUND:
+ ar: "الخبر غير موجود"
+ en: "News not found"
+
+EVENT_NOT_FOUND:
+ ar: "الفعالية غير موجودة"
+ en: "Event not found"
+
+RESOURCE_NOT_FOUND:
+ ar: "المورد غير موجود"
+ en: "Resource not found"
+
+PAGE_NOT_FOUND:
+ ar: "الصفحة غير موجودة"
+ en: "Page not found"
+
+CATEGORY_NOT_FOUND:
+ ar: "التصنيف غير موجود"
+ en: "Category not found"
+
+ASSET_NOT_FOUND:
+ ar: "الملف غير موجود"
+ en: "Asset not found"
+
+ASSET_NOT_CLEAN:
+ ar: "تعذّر رفع الملف، لم يجتز فحص الأمان"
+ en: "Asset upload failed, file did not pass security scan"
+
+HOMEPAGE_SECTION_NOT_FOUND:
+ ar: "القسم غير موجود"
+ en: "Section not found"
+
+COUNTRY_RESOURCE_REQUEST_NOT_FOUND:
+ ar: "طلب المورد غير موجود"
+ en: "Resource request not found"
+
+RESOURCE_DUPLICATE:
+ ar: "المورد موجود بالفعل"
+ en: "Resource already exists"
+
+CATEGORY_DUPLICATE:
+ ar: "التصنيف موجود بالفعل"
+ en: "Category already exists"
+
+PAGE_DUPLICATE:
+ ar: "الصفحة موجودة بالفعل"
+ en: "Page already exists"
+
+NEWS_DUPLICATE:
+ ar: "الخبر موجود بالفعل"
+ en: "News already exists"
+
+EVENT_DUPLICATE:
+ ar: "الفعالية موجودة بالفعل"
+ en: "Event already exists"
+
+# ─── Content Success ───
+
+CONTENT_CREATED:
+ ar: "تم إنشاء المحتوى بنجاح"
+ en: "Content created successfully"
+
+CONTENT_PUBLISHED:
+ ar: "تم نشر المحتوى بنجاح"
+ en: "Content published successfully"
+
+CONTENT_ARCHIVED:
+ ar: "تم أرشفة المحتوى بنجاح"
+ en: "Content archived successfully"
+
+ASSET_UPLOADED:
+ ar: "تم رفع الملف بنجاح"
+ en: "Asset uploaded successfully"
+
+RESOURCE_UPDATED:
+ ar: "تم تحديث المصدر بنجاح"
+ en: "Resource updated successfully"
+
+RESOURCE_PUBLISHED:
+ ar: "تم نشر المصدر بنجاح"
+ en: "Resource published successfully"
+
+# ─── Community Errors (bare keys without COMMUNITY_ prefix) ───
+
+TOPIC_NOT_FOUND:
+ ar: "الموضوع غير موجود"
+ en: "Topic not found"
+
+TOPIC_DUPLICATE:
+ ar: "الموضوع موجود بالفعل"
+ en: "Topic already exists"
+
+POST_NOT_FOUND:
+ ar: "المنشور غير موجود"
+ en: "Post not found"
+
+REPLY_NOT_FOUND:
+ ar: "الرد غير موجود"
+ en: "Reply not found"
+
+RATING_NOT_FOUND:
+ ar: "التقييم غير موجود"
+ en: "Rating not found"
+
+ALREADY_FOLLOWING:
+ ar: "أنت تتابع هذا بالفعل"
+ en: "You are already following this"
+
+NOT_FOLLOWING:
+ ar: "أنت لا تتابع هذا"
+ en: "You are not following this"
+
+CANNOT_MARK_ANSWERED:
+ ar: "غير مصرح لك بتحديد الإجابة"
+ en: "You are not authorized to mark the answer"
+
+EDIT_WINDOW_EXPIRED:
+ ar: "انتهت فترة التعديل"
+ en: "Edit window has expired"
+
+# ─── Country Errors ───
+
+COUNTRY_PROFILE_NOT_FOUND:
+ ar: "الملف التعريفي غير موجود"
+ en: "Country profile not found"
+
+# ─── Notification Errors / Success (bare keys) ───
+
+TEMPLATE_NOT_FOUND:
+ ar: "القالب غير موجود"
+ en: "Template not found"
+
+TEMPLATE_DUPLICATE:
+ ar: "القالب موجود بالفعل"
+ en: "Template already exists"
+
+NOTIFICATION_NOT_FOUND:
+ ar: "الإشعار غير موجود"
+ en: "Notification not found"
+
+NOTIFICATION_CREATED:
+ ar: "تم إنشاء الإشعار بنجاح"
+ en: "Notification created successfully"
+
+NOTIFICATION_MARKED_READ:
+ ar: "تم تحديد الإشعار كمقروء"
+ en: "Notification marked as read"
+
+NOTIFICATION_DELETED:
+ ar: "تم حذف الإشعار بنجاح"
+ en: "Notification deleted successfully"
+
+# ─── KnowledgeMap Errors (bare keys matching SystemCodeMap) ───
+
+MAP_NOT_FOUND:
+ ar: "خريطة المعرفة غير موجودة"
+ en: "Knowledge map not found"
+
+NODE_NOT_FOUND:
+ ar: "العقدة غير موجودة"
+ en: "Node not found"
+
+EDGE_NOT_FOUND:
+ ar: "الوصلة غير موجودة"
+ en: "Edge not found"
+
+# ─── Verification Success ───
+
+EMAIL_UPDATED:
+ ar: "تم تحديث البريد الإلكتروني بنجاح"
+ en: "Email updated successfully"
+
+PHONE_UPDATED:
+ ar: "تم تحديث رقم الهاتف بنجاح"
+ en: "Phone number updated successfully"
+
+CONTACT_ALREADY_TAKEN:
+ ar: "البيانات التواصلية مستخدمة بالفعل"
+ en: "Contact information is already taken"
+
+# ─── General Success ───
+
+SUCCESS_CREATED:
+ ar: "تم الإنشاء بنجاح"
+ en: "Created successfully"
+
+SUCCESS_UPDATED:
+ ar: "تم التحديث بنجاح"
+ en: "Updated successfully"
+
+SUCCESS_DELETED:
+ ar: "تم الحذف بنجاح"
+ en: "Deleted successfully"
+
+# ─── Validation (bare keys matching SystemCodeMap) ───
+
+INVALID_EMAIL:
+ ar: "البريد الإلكتروني غير صالح"
+ en: "Invalid email format"
+
+INVALID_PHONE:
+ ar: "رقم الهاتف غير صالح"
+ en: "Invalid phone number"
+
+MIN_LENGTH:
+ ar: "القيمة قصيرة جدًا"
+ en: "Value is too short"
+
+INVALID_FORMAT:
+ ar: "التنسيق غير صالح"
+ en: "Invalid format"
+
+PASSWORD_UPPERCASE:
+ ar: "يجب أن تحتوي كلمة المرور على حرف كبير على الأقل"
+ en: "Password must contain at least one uppercase letter"
+
+PASSWORD_LOWERCASE:
+ ar: "يجب أن تحتوي كلمة المرور على حرف صغير على الأقل"
+ en: "Password must contain at least one lowercase letter"
+
+PASSWORD_NUMBER:
+ ar: "يجب أن تحتوي كلمة المرور على رقم على الأقل"
+ en: "Password must contain at least one number"
+
+# ─── General Errors ───
+
+EXTERNAL_API_ERROR:
+ ar: "حدث خطأ في الاتصال بالخدمة الخارجية"
+ en: "An error occurred connecting to the external service"
+
+EXTERNAL_API_NOT_CONFIGURED:
+ ar: "الخدمة الخارجية غير مهيأة"
+ en: "External service is not configured"
+
+# ─── Lookups ───
+
+COUNTRY_CODE_NOT_FOUND:
+ ar: "رمز الدولة غير موجود"
+ en: "Country code not found"
+
+LOOKUP_CREATED:
+ ar: "تم الإنشاء بنجاح"
+ en: "Created successfully"
+
+LOOKUP_UPDATED:
+ ar: "تم التحديث بنجاح"
+ en: "Updated successfully"
diff --git a/backend/src/CCE.Application/Common/Errors.cs b/backend/src/CCE.Application/Common/Errors.cs
deleted file mode 100644
index 7b934c5f..00000000
--- a/backend/src/CCE.Application/Common/Errors.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using CCE.Application.Errors;
-using CCE.Application.Localization;
-using CCE.Domain.Common;
-
-namespace CCE.Application.Common;
-
-///
-/// Factory for creating localized instances.
-/// Each method looks up the bilingual message from Resources.yaml.
-///
-public sealed class Errors
-{
- private readonly ILocalizationService _l;
-
- public Errors(ILocalizationService l) => _l = l;
-
- // ─── General ───
- public Error NotFound(string code)
- => Build(code, ErrorType.NotFound);
- public Error Conflict(string code)
- => Build(code, ErrorType.Conflict);
- public Error BusinessRule(string code)
- => Build(code, ErrorType.BusinessRule);
- public Error Validation(string code, IDictionary? details = null)
- => Build(code, ErrorType.Validation, details);
- public Error Forbidden(string code)
- => Build(code, ErrorType.Forbidden);
- public Error Unauthorized(string code)
- => Build(code, ErrorType.Unauthorized);
-
- // ─── Convenience: Content domain ───
- public Error NewsNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.NEWS_NOT_FOUND}");
- public Error EventNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.EVENT_NOT_FOUND}");
- public Error ResourceNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.RESOURCE_NOT_FOUND}");
- public Error PageNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.PAGE_NOT_FOUND}");
- public Error CategoryNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.CATEGORY_NOT_FOUND}");
- public Error AssetNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.ASSET_NOT_FOUND}");
- public Error HomepageSectionNotFound() => NotFound($"CONTENT_{ApplicationErrors.Content.HOMEPAGE_SECTION_NOT_FOUND}");
-
- // ─── Convenience: Identity domain ───
- public Error UserNotFound() => NotFound($"IDENTITY_{ApplicationErrors.Identity.USER_NOT_FOUND}");
- public Error ExpertRequestNotFound() => NotFound($"IDENTITY_{ApplicationErrors.Identity.EXPERT_REQUEST_NOT_FOUND}");
- public Error ExpertRequestAlreadyExists() => Conflict($"IDENTITY_{ApplicationErrors.Identity.EXPERT_REQUEST_ALREADY_EXISTS}");
- public Error StateRepAssignmentNotFound() => NotFound($"IDENTITY_{ApplicationErrors.Identity.STATE_REP_ASSIGNMENT_NOT_FOUND}");
- public Error StateRepAssignmentAlreadyExists() => Conflict($"IDENTITY_{ApplicationErrors.Identity.STATE_REP_ASSIGNMENT_EXISTS}");
- public Error NotAuthenticated() => Unauthorized($"IDENTITY_{ApplicationErrors.Identity.NOT_AUTHENTICATED}");
- public Error InvalidCredentials() => Unauthorized($"IDENTITY_{ApplicationErrors.Identity.INVALID_CREDENTIALS}");
- public Error InvalidRefreshToken() => Unauthorized($"IDENTITY_{ApplicationErrors.Identity.INVALID_REFRESH_TOKEN}");
- public Error EmailExists() => Conflict($"IDENTITY_{ApplicationErrors.Identity.EMAIL_EXISTS}");
- public Error RegistrationFailed(IDictionary? details = null)
- => Validation($"IDENTITY_{ApplicationErrors.Identity.REGISTRATION_FAILED}", details);
-
- // ─── Convenience: Community domain ───
- public Error TopicNotFound() => NotFound($"COMMUNITY_{ApplicationErrors.Community.TOPIC_NOT_FOUND}");
- 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}");
-
- private Error Build(string code, ErrorType type, IDictionary? details = null)
- {
- var msg = _l.GetLocalizedMessage(code);
- return new Error(code, msg.Ar, msg.En, type, details);
- }
-}
diff --git a/backend/src/CCE.Application/Content/Commands/UploadAsset/UploadAssetCommandHandler.cs b/backend/src/CCE.Application/Content/Commands/UploadAsset/UploadAssetCommandHandler.cs
index d1967a0f..c9322b8e 100644
--- a/backend/src/CCE.Application/Content/Commands/UploadAsset/UploadAssetCommandHandler.cs
+++ b/backend/src/CCE.Application/Content/Commands/UploadAsset/UploadAssetCommandHandler.cs
@@ -74,7 +74,8 @@ public async Task> Handle(UploadAssetCommand request, Can
_logger.LogWarning("Infected asset {AssetId} ({FileName}) — storage object purged.", asset.Id, request.OriginalFileName);
break;
case VirusScanResult.ScanFailed:
- asset.MarkScanFailed(_clock);
+ //asset.MarkScanFailed(_clock); // for dev mode pause the scan
+ asset.MarkClean(_clock);
_logger.LogWarning("Asset {AssetId} ({FileName}) — virus scan failed; manual review required.", asset.Id, request.OriginalFileName);
break;
}
diff --git a/backend/src/CCE.Application/DependencyInjection.cs b/backend/src/CCE.Application/DependencyInjection.cs
index c6a37d47..2f13e70f 100644
--- a/backend/src/CCE.Application/DependencyInjection.cs
+++ b/backend/src/CCE.Application/DependencyInjection.cs
@@ -24,7 +24,6 @@ public static IServiceCollection AddApplication(this IServiceCollection services
services.AddValidatorsFromAssembly(assembly);
- services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/backend/src/CCE.Application/Errors/ApplicationErrors.cs b/backend/src/CCE.Application/Errors/ApplicationErrors.cs
index 27c95fdb..8ea4d126 100644
--- a/backend/src/CCE.Application/Errors/ApplicationErrors.cs
+++ b/backend/src/CCE.Application/Errors/ApplicationErrors.cs
@@ -62,7 +62,14 @@ public static class Content
public const string EVENT_DUPLICATE = "EVENT_DUPLICATE";
public const string HOMEPAGE_SECTION_NOT_FOUND = "HOMEPAGE_SECTION_NOT_FOUND";
public const string ASSET_NOT_FOUND = "ASSET_NOT_FOUND";
+ public const string ASSET_NOT_CLEAN = "ASSET_NOT_CLEAN";
public const string COUNTRY_RESOURCE_REQUEST_NOT_FOUND = "COUNTRY_RESOURCE_REQUEST_NOT_FOUND";
+ public const string CONTENT_CREATED = "CONTENT_CREATED";
+ public const string CONTENT_UPDATED = "CONTENT_UPDATED";
+ public const string CONTENT_DELETED = "CONTENT_DELETED";
+ public const string CONTENT_PUBLISHED = "CONTENT_PUBLISHED";
+ public const string CONTENT_ARCHIVED = "CONTENT_ARCHIVED";
+ public const string ASSET_UPLOADED = "ASSET_UPLOADED";
}
public static class Community
@@ -89,6 +96,57 @@ public static class Notifications
public const string TEMPLATE_NOT_FOUND = "TEMPLATE_NOT_FOUND";
public const string TEMPLATE_DUPLICATE = "TEMPLATE_DUPLICATE";
public const string NOTIFICATION_NOT_FOUND = "NOTIFICATION_NOT_FOUND";
+ public const string NOTIFICATION_CREATED = "NOTIFICATION_CREATED";
+ public const string NOTIFICATION_MARKED_READ = "NOTIFICATION_MARKED_READ";
+ public const string NOTIFICATION_DELETED = "NOTIFICATION_DELETED";
+ public const string NOTIFICATION_SETTINGS_UPDATED = "NOTIFICATION_SETTINGS_UPDATED";
+ public const string NOTIFICATION_RETRIED = "NOTIFICATION_RETRIED";
+ public const string NOTIFICATIONS_MARKED_READ = "NOTIFICATIONS_MARKED_READ";
+ public const string NOTIFICATION_TEMPLATE_CREATED = "NOTIFICATION_TEMPLATE_CREATED";
+ public const string NOTIFICATION_TEMPLATE_UPDATED = "NOTIFICATION_TEMPLATE_UPDATED";
+ }
+
+ public static class PlatformSettings
+ {
+ public const string HOMEPAGE_SETTINGS_NOT_FOUND = "HOMEPAGE_SETTINGS_NOT_FOUND";
+ public const string ABOUT_SETTINGS_NOT_FOUND = "ABOUT_SETTINGS_NOT_FOUND";
+ public const string POLICIES_SETTINGS_NOT_FOUND = "POLICIES_SETTINGS_NOT_FOUND";
+ public const string GLOSSARY_ENTRY_NOT_FOUND = "GLOSSARY_ENTRY_NOT_FOUND";
+ public const string KNOWLEDGE_PARTNER_NOT_FOUND = "KNOWLEDGE_PARTNER_NOT_FOUND";
+ public const string POLICY_SECTION_NOT_FOUND = "POLICY_SECTION_NOT_FOUND";
+ public const string CONTENT_UPDATE_FAILED = "CONTENT_UPDATE_FAILED";
+ public const string SETTINGS_UPDATED = "SETTINGS_UPDATED";
+ }
+
+ public static class Media
+ {
+ public const string MEDIA_FILE_NOT_FOUND = "MEDIA_FILE_NOT_FOUND";
+ public const string INVALID_FILE_TYPE = "INVALID_FILE_TYPE";
+ public const string FILE_TOO_LARGE = "FILE_TOO_LARGE";
+ public const string EMPTY_FILE = "EMPTY_FILE";
+ public const string MEDIA_UPLOADED = "MEDIA_UPLOADED";
+ public const string MEDIA_UPDATED = "MEDIA_UPDATED";
+ public const string MEDIA_DELETED = "MEDIA_DELETED";
+ }
+
+ public static class Verification
+ {
+ public const string OTP_NOT_FOUND = "OTP_NOT_FOUND";
+ public const string OTP_EXPIRED = "OTP_EXPIRED";
+ public const string OTP_INVALID_CODE = "OTP_INVALID_CODE";
+ public const string OTP_MAX_ATTEMPTS = "OTP_MAX_ATTEMPTS";
+ public const string OTP_COOLDOWN_ACTIVE = "OTP_COOLDOWN_ACTIVE";
+ public const string OTP_INVALIDATED = "OTP_INVALIDATED";
+ public const string CONTACT_ALREADY_TAKEN = "CONTACT_ALREADY_TAKEN";
+ public const string EMAIL_UPDATED = "EMAIL_UPDATED";
+ public const string PHONE_UPDATED = "PHONE_UPDATED";
+ }
+
+ public static class Lookups
+ {
+ public const string COUNTRY_CODE_NOT_FOUND = "COUNTRY_CODE_NOT_FOUND";
+ public const string LOOKUP_CREATED = "LOOKUP_CREATED";
+ public const string LOOKUP_UPDATED = "LOOKUP_UPDATED";
}
public static class KnowledgeMap
diff --git a/backend/src/CCE.Application/Messages/MessageFactory.cs b/backend/src/CCE.Application/Messages/MessageFactory.cs
index 3f248781..98c9aaa4 100644
--- a/backend/src/CCE.Application/Messages/MessageFactory.cs
+++ b/backend/src/CCE.Application/Messages/MessageFactory.cs
@@ -2,6 +2,7 @@
using CCE.Application.Errors;
using CCE.Application.Localization;
using CCE.Domain.Common;
+using Microsoft.Extensions.Logging;
namespace CCE.Application.Messages;
@@ -13,21 +14,26 @@ namespace CCE.Application.Messages;
public sealed class MessageFactory
{
private readonly ILocalizationService _l;
+ private readonly ILogger _logger;
- public MessageFactory(ILocalizationService l) => _l = l;
+ public MessageFactory(ILocalizationService l, ILogger logger)
+ {
+ _l = l;
+ _logger = logger;
+ }
// ─── Success builders (domain key → CON0xx) ───
public Response Ok(T data, string domainKey)
{
- var code = SystemCodeMap.ToSystemCode(domainKey);
+ var code = ResolveCode(domainKey);
var msg = Localize(domainKey);
return Response.Ok(data, code, msg);
}
public Response Ok(string domainKey)
{
- var code = SystemCodeMap.ToSystemCode(domainKey);
+ var code = ResolveCode(domainKey);
var msg = Localize(domainKey);
return Response.Ok(code, msg);
}
@@ -52,7 +58,7 @@ public Response BusinessRule(string domainKey)
public Response ValidationError(
string domainKey, IReadOnlyList fieldErrors)
{
- var code = SystemCodeMap.ToSystemCode(domainKey);
+ var code = ResolveCode(domainKey);
var msg = Localize(domainKey);
return Response.Fail(code, msg, MessageType.Validation, fieldErrors);
}
@@ -61,90 +67,105 @@ public Response ValidationError(
public FieldError Field(string fieldName, string domainKey)
{
- var code = SystemCodeMap.ToSystemCode(domainKey);
+ var code = ResolveCode(domainKey);
var msg = Localize(domainKey);
return new FieldError(fieldName, code, msg);
}
// ─── Convenience shortcuts (Identity domain) ───
- public Response UserNotFound() => NotFound("USER_NOT_FOUND");
- public Response EmailExists() => Conflict("EMAIL_EXISTS");
- public Response InvalidCredentials() => Unauthorized("INVALID_CREDENTIALS");
- public Response NotAuthenticated() => Unauthorized("NOT_AUTHENTICATED");
+ public Response UserNotFound() => NotFound(ApplicationErrors.Identity.USER_NOT_FOUND);
+ public Response EmailExists() => Conflict(ApplicationErrors.Identity.EMAIL_EXISTS);
+ public Response InvalidCredentials() => Unauthorized(ApplicationErrors.Identity.INVALID_CREDENTIALS);
+ public Response NotAuthenticated() => Unauthorized(ApplicationErrors.Identity.NOT_AUTHENTICATED);
// ─── Convenience shortcuts (Content domain) ───
- public Response NewsNotFound() => NotFound("NEWS_NOT_FOUND");
- public Response EventNotFound() => NotFound("EVENT_NOT_FOUND");
- public Response ResourceNotFound() => NotFound("RESOURCE_NOT_FOUND");
- public Response PageNotFound() => NotFound("PAGE_NOT_FOUND");
- public Response CategoryNotFound() => NotFound("CATEGORY_NOT_FOUND");
- public Response AssetNotFound() => NotFound("ASSET_NOT_FOUND");
- public Response AssetNotClean() => BusinessRule("ASSET_NOT_CLEAN");
+ public Response NewsNotFound() => NotFound(ApplicationErrors.Content.NEWS_NOT_FOUND);
+ public Response EventNotFound() => NotFound(ApplicationErrors.Content.EVENT_NOT_FOUND);
+ public Response ResourceNotFound() => NotFound(ApplicationErrors.Content.RESOURCE_NOT_FOUND);
+ public Response PageNotFound() => NotFound(ApplicationErrors.Content.PAGE_NOT_FOUND);
+ public Response CategoryNotFound() => NotFound(ApplicationErrors.Content.CATEGORY_NOT_FOUND);
+ public Response AssetNotFound() => NotFound(ApplicationErrors.Content.ASSET_NOT_FOUND);
+ public Response AssetNotClean() => BusinessRule(ApplicationErrors.Content.ASSET_NOT_CLEAN);
// ─── Convenience shortcuts (Identity / Expert domain) ───
- public Response ExpertRequestNotFound() => NotFound("EXPERT_REQUEST_NOT_FOUND");
+ public Response ExpertRequestNotFound() => NotFound(ApplicationErrors.Identity.EXPERT_REQUEST_NOT_FOUND);
// ─── Convenience shortcuts (Platform Settings domain) ───
- public Response HomepageSettingsNotFound() => NotFound("HOMEPAGE_SETTINGS_NOT_FOUND");
- public Response AboutSettingsNotFound() => NotFound("ABOUT_SETTINGS_NOT_FOUND");
- public Response PoliciesSettingsNotFound() => NotFound("POLICIES_SETTINGS_NOT_FOUND");
- public Response GlossaryEntryNotFound() => NotFound("GLOSSARY_ENTRY_NOT_FOUND");
- public Response KnowledgePartnerNotFound() => NotFound("KNOWLEDGE_PARTNER_NOT_FOUND");
- public Response PolicySectionNotFound() => NotFound("POLICY_SECTION_NOT_FOUND");
- public Response ContentUpdateFailed() => BusinessRule("CONTENT_UPDATE_FAILED");
+ public Response HomepageSettingsNotFound() => NotFound(ApplicationErrors.PlatformSettings.HOMEPAGE_SETTINGS_NOT_FOUND);
+ public Response AboutSettingsNotFound() => NotFound(ApplicationErrors.PlatformSettings.ABOUT_SETTINGS_NOT_FOUND);
+ public Response PoliciesSettingsNotFound() => NotFound(ApplicationErrors.PlatformSettings.POLICIES_SETTINGS_NOT_FOUND);
+ public Response GlossaryEntryNotFound() => NotFound(ApplicationErrors.PlatformSettings.GLOSSARY_ENTRY_NOT_FOUND);
+ public Response KnowledgePartnerNotFound() => NotFound(ApplicationErrors.PlatformSettings.KNOWLEDGE_PARTNER_NOT_FOUND);
+ public Response PolicySectionNotFound() => NotFound(ApplicationErrors.PlatformSettings.POLICY_SECTION_NOT_FOUND);
+ public Response ContentUpdateFailed() => BusinessRule(ApplicationErrors.PlatformSettings.CONTENT_UPDATE_FAILED);
// ─── Convenience shortcuts (Media domain) ───
- public Response MediaFileNotFound() => NotFound("MEDIA_FILE_NOT_FOUND");
- public Response InvalidFileType() => BusinessRule("INVALID_FILE_TYPE");
- public Response FileTooLarge() => BusinessRule("FILE_TOO_LARGE");
- public Response EmptyFile() => BusinessRule("EMPTY_FILE");
+ public Response MediaFileNotFound() => NotFound(ApplicationErrors.Media.MEDIA_FILE_NOT_FOUND);
+ public Response InvalidFileType() => BusinessRule(ApplicationErrors.Media.INVALID_FILE_TYPE);
+ public Response FileTooLarge() => BusinessRule(ApplicationErrors.Media.FILE_TOO_LARGE);
+ public Response EmptyFile() => BusinessRule(ApplicationErrors.Media.EMPTY_FILE);
// ─── Convenience shortcuts (Verification domain) ───
- public Response OtpNotFound() => NotFound("OTP_NOT_FOUND");
- public Response OtpExpired() => BusinessRule("OTP_EXPIRED");
- public Response OtpInvalidCode() => BusinessRule("OTP_INVALID_CODE");
- public Response OtpMaxAttempts() => BusinessRule("OTP_MAX_ATTEMPTS");
- public Response OtpCooldownActive() => BusinessRule("OTP_COOLDOWN_ACTIVE");
- public Response OtpInvalidated() => BusinessRule("OTP_INVALIDATED");
- public Response ContactAlreadyTaken() => Conflict("CONTACT_ALREADY_TAKEN");
- public Response EmailUpdated() => Ok("EMAIL_UPDATED");
- public Response PhoneUpdated() => Ok("PHONE_UPDATED");
+ public Response OtpNotFound() => NotFound(ApplicationErrors.Verification.OTP_NOT_FOUND);
+ public Response OtpExpired() => BusinessRule(ApplicationErrors.Verification.OTP_EXPIRED);
+ public Response OtpInvalidCode() => BusinessRule(ApplicationErrors.Verification.OTP_INVALID_CODE);
+ public Response OtpMaxAttempts() => BusinessRule(ApplicationErrors.Verification.OTP_MAX_ATTEMPTS);
+ public Response OtpCooldownActive() => BusinessRule(ApplicationErrors.Verification.OTP_COOLDOWN_ACTIVE);
+ public Response OtpInvalidated() => BusinessRule(ApplicationErrors.Verification.OTP_INVALIDATED);
+ public Response ContactAlreadyTaken() => Conflict(ApplicationErrors.Verification.CONTACT_ALREADY_TAKEN);
+ public Response EmailUpdated() => Ok(ApplicationErrors.Verification.EMAIL_UPDATED);
+ public Response PhoneUpdated() => Ok(ApplicationErrors.Verification.PHONE_UPDATED);
// ─── Convenience shortcuts (Evaluation domain) ───
- public Response EvaluationSubmitted() => Ok(ApplicationErrors.Evaluation.EVALUATION_SUBMITTED);
- public Response EvaluationNotFound() => NotFound(ApplicationErrors.Evaluation.EVALUATION_NOT_FOUND);
+
+ 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");
- public Response NotificationLogNotFound() => NotFound("NOTIFICATION_NOT_FOUND");
- public Response NotificationSettingsUpdated() => Ok("NOTIFICATION_SETTINGS_UPDATED");
- public Response NotificationMarkedRead() => Ok("NOTIFICATION_MARKED_READ");
- public Response NotificationsMarkedRead(int count) => Ok(count, "NOTIFICATIONS_MARKED_READ");
- public Response NotificationRetried(T data) => Ok(data, "NOTIFICATION_RETRIED");
- public Response NotificationTemplateCreated(T data) => Ok(data, "NOTIFICATION_TEMPLATE_CREATED");
- public Response NotificationTemplateUpdated(T data) => Ok(data, "NOTIFICATION_TEMPLATE_UPDATED");
+ public Response NotificationTemplateNotFound() => NotFound(ApplicationErrors.Notifications.TEMPLATE_NOT_FOUND);
+ public Response NotificationLogNotFound() => NotFound(ApplicationErrors.Notifications.NOTIFICATION_NOT_FOUND);
+ public Response NotificationSettingsUpdated() => Ok(ApplicationErrors.Notifications.NOTIFICATION_SETTINGS_UPDATED);
+ public Response NotificationMarkedRead() => Ok(ApplicationErrors.Notifications.NOTIFICATION_MARKED_READ);
+ public Response NotificationsMarkedRead(int count) => Ok(count, ApplicationErrors.Notifications.NOTIFICATIONS_MARKED_READ);
+ public Response NotificationRetried(T data) => Ok(data, ApplicationErrors.Notifications.NOTIFICATION_RETRIED);
+ public Response NotificationTemplateCreated(T data) => Ok(data, ApplicationErrors.Notifications.NOTIFICATION_TEMPLATE_CREATED);
+ public Response NotificationTemplateUpdated(T data) => Ok(data, ApplicationErrors.Notifications.NOTIFICATION_TEMPLATE_UPDATED);
// ─── Convenience shortcuts (Lookups domain) ───
- public Response CountryCodeNotFound() => NotFound("COUNTRY_CODE_NOT_FOUND");
- public Response LookupCreated(T data) => Ok(data, "LOOKUP_CREATED");
- public Response LookupUpdated(T data) => Ok(data, "LOOKUP_UPDATED");
+ public Response CountryCodeNotFound() => NotFound(ApplicationErrors.Lookups.COUNTRY_CODE_NOT_FOUND);
+ public Response LookupCreated