# MissionService - Service Documentation ## 1. Overview **Purpose**: Gamification microservice that manages missions (tasks users complete for rewards), daily check-in streaks with tiered bonus points, and reward tracking. Supports mission types including video watching, link clicking, content uploading, friend invitations, daily check-ins, and social actions. **Port**: 5000 (development, via launchSettings.json), 8080 (Docker/production) **Database**: PostgreSQL (`mission_service` default in appsettings.json, configurable via `ConnectionStrings:DefaultConnection` or `DATABASE_URL`) **Framework**: .NET 10.0, C# 14, Clean Architecture + CQRS **Solution file**: `MissionService.slnx` **Migration**: `20260117134348_InitialCreate` — auto-applied on startup via `dbContext.Database.MigrateAsync()` --- ## 2. API Endpoints ### MissionsController — `api/v1/missions` (Authorize) | Method | Route | Auth | Description | |--------|-------|------|-------------| | GET | `/api/v1/missions` | JWT required | Get all available missions for the authenticated user. Returns `MissionsListResult`. | | GET | `/api/v1/missions/category/{category}` | AllowAnonymous | Get missions filtered by category name (e.g., "Daily", "Weekly"). Returns `MissionsListResult`. | | GET | `/api/v1/missions/{id:guid}` | AllowAnonymous | Get mission details by ID. Returns `MissionDetailsResult` or 404. Optionally includes user progress if authenticated. | | POST | `/api/v1/missions/{id:guid}/start` | JWT required | Start a mission task for the user. Returns `StartTaskResult`. | | PUT | `/api/v1/missions/tasks/{taskId:guid}/progress` | JWT required | Update task progress. Body: `UpdateProgressRequest(CurrentValue, Evidence?)`. Returns `UpdateProgressResult`. | | POST | `/api/v1/missions/tasks/{taskId:guid}/claim` | JWT required | Claim reward for a completed task. Returns `ClaimRewardResult`. | ### CheckInsController — `api/v1/checkins` (Authorize) | Method | Route | Auth | Description | |--------|-------|------|-------------| | POST | `/api/v1/checkins` | JWT required | Perform daily check-in. Returns `CheckInResult` with streak info and points earned. | | GET | `/api/v1/checkins/status` | JWT required | Get current check-in status (streak, total, can check-in today, next reward preview). | | GET | `/api/v1/checkins/history?year=&month=` | JWT required | Get check-in history for a specific month. Defaults to current month. | | GET | `/api/v1/checkins/leaderboard?count=10` | AllowAnonymous | Get check-in streak leaderboard (max 100 entries). | | GET | `/api/v1/checkins/config` | AllowAnonymous | Get streak bonus tier configuration for client display. | ### AdminController — `api/v1/admin` (Authorize Roles="Admin") | Method | Route | Description | |--------|-------|-------------| | GET | `/api/v1/admin/checkins/users/{userId:guid}` | Get a user's check-in profile details. | | POST | `/api/v1/admin/checkins/users/{userId:guid}/reset-streak` | Reset a user's current streak to 0. | | GET | `/api/v1/admin/checkins/top-streaks?count=20` | Get top streak users (max 100). | | GET | `/api/v1/admin/tasks/pending-verification` | Get all tasks pending admin verification. | | POST | `/api/v1/admin/tasks/{taskId:guid}/approve` | Approve a pending verification task. | | POST | `/api/v1/admin/tasks/{taskId:guid}/reject` | Reject a pending verification task. Body: `RejectTaskRequest(Reason)`. | | GET | `/api/v1/admin/tasks/users/{userId:guid}` | Get all tasks for a specific user. | ### AdminMissionsController — `api/v1/admin/missions` (Authorize Roles="Admin") | Method | Route | Description | |--------|-------|-------------| | GET | `/api/v1/admin/missions` | Get all active missions (admin view with full details). | | POST | `/api/v1/admin/missions` | Create a new mission. Body: `CreateMissionRequest`. Returns 201. | | GET | `/api/v1/admin/missions/{id:guid}` | Get mission by ID (admin view). | | POST | `/api/v1/admin/missions/{id:guid}/activate` | Activate a draft or paused mission. | | POST | `/api/v1/admin/missions/{id:guid}/pause` | Pause an active mission. | | POST | `/api/v1/admin/missions/{id:guid}/archive` | Archive a mission (any status). | ### Health Endpoints | Route | Description | |-------|-------------| | `/health` | Full health check (includes PostgreSQL). | | `/health/live` | Liveness probe (app is running). | | `/health/ready` | Readiness probe (includes PostgreSQL). | --- ## 3. Commands ### PerformCheckInCommand - **File**: `Application/Commands/CheckInCommands.cs` - **Parameters**: `Guid UserId` - **Result**: `CheckInResult(Success, CurrentStreak, DailyPoints, StreakBonus, MilestoneBonus, TotalPoints, IsMilestone, Message)` - **Behavior**: Gets or creates user's check-in profile, validates not already checked in today, calculates streak continuation or reset, applies tiered bonus config, saves CheckInDay record. ### StartMissionTaskCommand - **File**: `Application/Commands/TaskCommands.cs` - **Parameters**: `Guid UserId, Guid MissionId` - **Result**: `StartTaskResult(Success, TaskId?, Message)` - **Behavior**: Validates mission exists and is available, checks no existing active task for this user+mission, checks max completions not reached, creates new `UserTask`. ### UpdateTaskProgressCommand - **File**: `Application/Commands/TaskCommands.cs` - **Parameters**: `Guid UserId, Guid TaskId, int CurrentValue, TaskEvidenceDto? Evidence` - **Result**: `UpdateProgressResult(Success, CurrentValue, TargetValue, PercentComplete, IsComplete, Message)` - **Behavior**: Validates task ownership and InProgress status, updates progress value, auto-submits evidence if provided and complete, auto-completes if no verification needed. ### ClaimTaskRewardCommand - **File**: `Application/Commands/TaskCommands.cs` - **Parameters**: `Guid UserId, Guid TaskId` - **Result**: `ClaimRewardResult(Success, PointsEarned?, Message)` - **Behavior**: Validates task ownership, completed status, and not already claimed. Looks up mission reward points, marks task as claimed. --- ## 4. Queries ### GetAvailableMissionsQuery - **Parameters**: `Guid UserId` - **Returns**: `MissionsListResult` — list of active missions with optional user task progress per mission. ### GetMissionDetailsQuery - **Parameters**: `Guid MissionId, Guid? UserId` - **Returns**: `MissionDetailsResult?` — full mission details (bilingual titles, reward, rules, frequency) with optional user progress. Returns null if not found. ### GetMissionsByCategoryQuery - **Parameters**: `string Category` (display name, e.g., "Daily") - **Returns**: `MissionsListResult` — active missions filtered by category. No user progress included. ### GetUserMissionProgressQuery - **Parameters**: `Guid UserId` - **Returns**: `UserMissionProgressResult` — aggregated stats (total, completed, in-progress counts, total points, active/completed mission lists). - **Note**: Query record is defined but no handler implementation exists in the codebase. ### GetCheckInStatusQuery - **Parameters**: `Guid UserId` - **Returns**: `CheckInStatusResult(CurrentStreak, LongestStreak, TotalCheckIns, LastCheckInDate, CanCheckInToday, NextReward)` — includes a preview of the next reward milestone. ### GetCheckInHistoryQuery - **Parameters**: `Guid UserId, int Year, int Month` - **Returns**: `CheckInHistoryResult(Year, Month, Days[])` — list of `CheckInDayDto(Date, PointsEarned, IsMilestone, StreakOnDay)` for the requested month. ### GetCheckInLeaderboardQuery - **Parameters**: `int Count` (default 10) - **Returns**: `CheckInLeaderboardResult(Entries[])` — ranked list by longest streak, then total check-ins. --- ## 5. Domain Model ### Aggregates #### Mission (Aggregate Root) - **File**: `Domain/AggregatesModel/MissionAggregate/Mission.cs` - **Properties**: Code (unique), TitleEn, TitleVi, DescriptionEn, DescriptionVi, Type (MissionType), Category (MissionCategory), Reward (MissionReward), Frequency (FrequencyType), MaxCompletions, StartDate, EndDate, Status (MissionStatus), Priority, Rules (collection of MissionRule) - **Behavior methods**: `Activate()` (Draft/Paused -> Active), `Pause()` (Active -> Paused), `Archive()` (any -> Archived), `AddRule(MissionRule)`, `IsAvailable()`, `UpdateDetails(...)` - **State machine**: Draft -> Active <-> Paused -> Archived; any -> Archived #### UserCheckIn (Aggregate Root) - **File**: `Domain/AggregatesModel/CheckInAggregate/UserCheckIn.cs` - **Properties**: UserId (unique), CurrentStreak, LongestStreak, TotalCheckIns, LastCheckInDate (DateOnly?), CheckInDays (collection of CheckInDay) - **Behavior methods**: `CanCheckInToday()`, `CheckIn(StreakBonusConfig)` — calculates streak continuation/reset and returns points, `GetMonthlyCheckIns(year, month)`, `ResetStreak()` (admin) #### UserTask (Aggregate Root) - **File**: `Domain/AggregatesModel/TaskAggregate/UserTask.cs` - **Properties**: UserId, MissionId, Status (TaskStatus), Progress (TaskProgress), Evidence (TaskEvidence?), Verification (VerificationResult?), RewardClaimed, StartedAt, CompletedAt, ClaimedAt - **Behavior methods**: `UpdateProgress(int)`, `SubmitEvidence(TaskEvidence)`, `Complete(VerificationResult)`, `AutoComplete()`, `ClaimReward()`, `Cancel()` - **State machine**: InProgress -> PendingVerification -> Completed/Rejected; InProgress -> Completed (auto); any (unclaimed) -> Cancelled #### UserReward (Aggregate Root) - **File**: `Domain/AggregatesModel/RewardAggregate/UserReward.cs` - **Properties**: UserId, SourceId (TaskId or CheckInId), Type (RewardType enum), Status (RewardStatus enum), Amount (RewardAmount), EarnedAt, ClaimedAt, ExpiresAt - **Factory methods**: `ForMission(...)`, `ForCheckIn(...)`, `ForMilestone(...)`, `ForReferral(...)` - **Behavior methods**: `Claim()`, `Expire()`, `Cancel(string reason)` ### Entities (non-root) #### MissionRule - **Properties**: RuleType, Operator, Value, Metadata (jsonb) - **Factory methods**: `MinDuration(seconds)`, `MinWatchPercent(percent)`, `SpecificUrl(url)` - **Owned by**: Mission (cascade delete) #### CheckInDay - **Properties**: Date (DateOnly), PointsEarned, IsMilestone, StreakOnDay - **Owned by**: UserCheckIn (cascade delete) ### Value Objects #### MissionReward - **Properties**: Points (decimal), MiningBoostPercent (decimal), ExperiencePoints (int), BadgeId (string?) - **Factory**: `Create(points, miningBoost, xp, badgeId)`, `PointsOnly(points)` #### TaskProgress - **Properties**: CurrentValue (int), TargetValue (int), LastUpdated (DateTime) - **Computed**: PercentComplete, IsComplete - **Methods**: `WithValue(int)`, `Increment(int)` #### TaskEvidence - **Properties**: Type (EvidenceType enum), Data, ScreenshotUrl, VideoUrl, CapturedAt - **Factory methods**: `WatchDuration(seconds)`, `Click(url)`, `Upload(fileUrl, isImage)`, `SocialProof(screenshotUrl)`, `InviteCode(code)` #### VerificationResult - **Properties**: IsValid, FailureReason, Method (VerificationMethod enum), VerifiedBy (Guid?), VerifiedAt - **Factory methods**: `AutoApproved()`, `AiApproved()`, `ManualApproved(adminId)`, `Rejected(reason, method, adminId?)` #### RewardAmount - **Properties**: Points (decimal), BonusPoints (decimal), Currency (string, default "MP") - **Computed**: TotalPoints - **Methods**: `WithBonus(additionalBonus)` #### StreakBonusConfig - **Properties**: BasePoints (decimal), Tiers (list of StreakTier) - **Default tiers**: Days 1-6: 2 MP/day; Day 7: 3 MP + 20 MP milestone; Days 8-13: 3 MP; Day 14: 4 MP + 35 MP milestone; Days 15-20: 4 MP; Day 21: 5 MP + 50 MP milestone; Days 22-29: 5 MP; Day 30+: 10 MP + 100 MP milestone - **Method**: `CalculateReward(streakDay)` returns (DailyPoints, StreakBonus, MilestoneBonus, IsMilestone) ### Enumerations (Type-safe enum pattern) #### MissionType | Id | Name | Description | |----|------|-------------| | 1 | Video | Watch video to earn rewards | | 2 | Click | Click on links/ads | | 3 | Upload | Upload user-generated content | | 4 | Invite | Invite friends | | 5 | CheckIn | Daily check-in | | 6 | Social | Social actions (like, share, subscribe) | #### MissionCategory | Id | Name | |----|------| | 1 | Daily | | 2 | Weekly | | 3 | Special | | 4 | Onboarding | | 5 | Event | #### MissionStatus | Id | Name | |----|------| | 1 | Draft | | 2 | Active | | 3 | Paused | | 4 | Expired | | 5 | Archived | #### FrequencyType | Id | Name | Description | |----|------|-------------| | 1 | Once | Complete once total | | 2 | Daily | Once per day | | 3 | Weekly | Once per week | | 4 | Unlimited | Unlimited times | #### TaskStatus | Id | Name | |----|------| | 1 | Pending | | 2 | InProgress | | 3 | PendingVerification | | 4 | Completed | | 5 | Rejected | | 6 | Cancelled | | 7 | Expired | ### Enums (C# enum) #### EvidenceType `WatchDuration = 1, ClickData = 2, UploadedContent = 3, SocialProof = 4, InviteCode = 5` #### VerificationMethod `Automatic = 1, AI = 2, Manual = 3` #### RewardType `MissionComplete = 1, CheckInDaily = 2, CheckInMilestone = 3, ReferralBonus = 4, SocialAction = 5` #### RewardStatus `Pending = 1, Claimed = 2, Expired = 3, Cancelled = 4` --- ## 6. Database Schema Database: PostgreSQL. All column names use snake_case. Migration: `20260117134348_InitialCreate`. ### Table: `missions` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | code | varchar(100) | NOT NULL, UNIQUE INDEX | | title_en | varchar(200) | NOT NULL | | title_vi | varchar(200) | NOT NULL | | description_en | varchar(2000) | nullable | | description_vi | varchar(2000) | nullable | | type_id | integer | NOT NULL (FK concept to mission_types) | | category_id | integer | NOT NULL (FK concept to mission_categories) | | reward_points | numeric(18,2) | NOT NULL (owned: MissionReward) | | reward_mining_boost | numeric(5,2) | NOT NULL (owned: MissionReward) | | reward_xp | integer | NOT NULL (owned: MissionReward) | | reward_badge_id | varchar(50) | nullable (owned: MissionReward) | | frequency_id | integer | NOT NULL (FK concept to frequency_types) | | max_completions | integer | NOT NULL | | start_date | timestamp with time zone | NOT NULL | | end_date | timestamp with time zone | nullable | | status_id | integer | NOT NULL (FK concept to mission_statuses) | | priority | integer | NOT NULL | **Indexes**: `IX_missions_code` (unique on code) ### Table: `mission_rules` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | mission_id | uuid | NOT NULL, FK -> missions(id) CASCADE | | rule_type | varchar(50) | NOT NULL | | operator | varchar(20) | NOT NULL | | value | varchar(500) | NOT NULL | | metadata | jsonb | nullable | **Indexes**: `IX_mission_rules_mission_id` ### Table: `user_tasks` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | user_id | uuid | NOT NULL | | mission_id | uuid | NOT NULL | | status_id | integer | NOT NULL | | progress_current | integer | NOT NULL (owned: TaskProgress) | | progress_target | integer | NOT NULL (owned: TaskProgress) | | progress_updated_at | timestamp with time zone | NOT NULL (owned: TaskProgress) | | evidence_type | integer | nullable (owned: TaskEvidence) | | evidence_data | varchar(4000) | nullable (owned: TaskEvidence) | | evidence_screenshot_url | varchar(500) | nullable (owned: TaskEvidence) | | evidence_video_url | varchar(500) | nullable (owned: TaskEvidence) | | evidence_captured_at | timestamp with time zone | nullable (owned: TaskEvidence) | | verification_valid | boolean | nullable (owned: VerificationResult) | | verification_failure_reason | varchar(500) | nullable (owned: VerificationResult) | | verification_method | integer | nullable (owned: VerificationResult) | | verification_by | uuid | nullable (owned: VerificationResult) | | verification_at | timestamp with time zone | nullable (owned: VerificationResult) | | reward_claimed | boolean | NOT NULL | | started_at | timestamp with time zone | NOT NULL | | completed_at | timestamp with time zone | nullable | | claimed_at | timestamp with time zone | nullable | **Indexes**: `IX_user_tasks_user_id`, `IX_user_tasks_mission_id`, `IX_user_tasks_user_id_mission_id` (composite) ### Table: `user_checkins` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | user_id | uuid | NOT NULL, UNIQUE INDEX | | current_streak | integer | NOT NULL | | longest_streak | integer | NOT NULL | | total_checkins | integer | NOT NULL | | last_checkin_date | date | nullable | **Indexes**: `IX_user_checkins_user_id` (unique) ### Table: `checkin_days` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | user_checkin_id | uuid | NOT NULL, FK -> user_checkins(id) CASCADE | | date | date | NOT NULL | | points_earned | numeric(18,2) | NOT NULL | | is_milestone | boolean | NOT NULL | | streak_on_day | integer | NOT NULL | **Indexes**: `IX_checkin_days_user_checkin_id_date` (unique composite) ### Table: `user_rewards` | Column | Type | Constraints | |--------|------|-------------| | id | uuid | PK, not generated | | user_id | uuid | NOT NULL | | source_id | uuid | NOT NULL, UNIQUE INDEX | | type | integer | NOT NULL (RewardType enum) | | status | integer | NOT NULL (RewardStatus enum) | | points | numeric(18,2) | NOT NULL (owned: RewardAmount) | | bonus_points | numeric(18,2) | NOT NULL (owned: RewardAmount) | | currency | varchar(10) | NOT NULL, default "MP" (owned: RewardAmount) | | earned_at | timestamp with time zone | NOT NULL | | claimed_at | timestamp with time zone | nullable | | expires_at | timestamp with time zone | nullable | **Indexes**: `IX_user_rewards_user_id`, `IX_user_rewards_source_id` (unique), `IX_user_rewards_status_expires_at` (composite) ### Lookup Tables (seeded) | Table | Columns | Seed Data | |-------|---------|-----------| | `mission_types` | id (int PK), name (varchar 50) | 1=Video, 2=Click, 3=Upload, 4=Invite, 5=CheckIn, 6=Social | | `mission_categories` | id (int PK), name (varchar 50) | 1=Daily, 2=Weekly, 3=Special, 4=Onboarding, 5=Event | | `mission_statuses` | id (int PK), name (varchar 50) | 1=Draft, 2=Active, 3=Paused, 4=Expired, 5=Archived | | `frequency_types` | id (int PK), name (varchar 50) | 1=Once, 2=Daily, 3=Weekly, 4=Unlimited | | `task_statuses` | id (int PK), name (varchar 50) | 1=Pending, 2=InProgress, 3=PendingVerification, 4=Completed, 5=Rejected, 6=Cancelled, 7=Expired | --- ## 7. Integration Events **No integration events are currently implemented.** The domain supports domain events via the `Entity.AddDomainEvent()` / `MissionDbContext.DispatchDomainEventsAsync()` infrastructure, but no domain event records or handlers exist in the codebase. No RabbitMQ integration event publishers or consumers are present. The `Domain/Events/` directory referenced in the template pattern does not exist in this service. --- ## 8. Dependencies ### NuGet Packages **API Layer** (`MissionService.API.csproj`): | Package | Version | |---------|---------| | MediatR | 12.4.1 | | FluentValidation | 11.11.0 | | FluentValidation.DependencyInjectionExtensions | 11.11.0 | | Microsoft.EntityFrameworkCore.Design | 10.0.2 | | Swashbuckle.AspNetCore | 7.2.0 | | Asp.Versioning.Mvc | 8.1.0 | | Asp.Versioning.Mvc.ApiExplorer | 8.1.0 | | AspNetCore.HealthChecks.NpgSql | 8.0.2 | | AspNetCore.HealthChecks.Redis | 8.0.1 | | Hellang.Middleware.ProblemDetails | 6.5.1 | | Serilog.AspNetCore | 8.0.3 | | Serilog.Sinks.Console | 6.0.0 | | Serilog.Sinks.Seq | 8.0.0 | | Microsoft.AspNetCore.Authentication.JwtBearer | 9.0.0 | **Domain Layer** (`MissionService.Domain.csproj`): | Package | Version | |---------|---------| | MediatR.Contracts | 2.0.1 | **Infrastructure Layer** (`MissionService.Infrastructure.csproj`): | Package | Version | |---------|---------| | Microsoft.EntityFrameworkCore | 10.0.0 | | Npgsql.EntityFrameworkCore.PostgreSQL | 10.0.0 | | Microsoft.EntityFrameworkCore.Tools | 10.0.0 | | MediatR | 12.4.1 | | Dapper | 2.1.35 | | Microsoft.Extensions.Http.Polly | 9.0.0 | | Polly | 8.5.0 | | StackExchange.Redis | 2.8.16 | **Shared** (`Directory.Build.props`): | Package | Version | |---------|---------| | Microsoft.SourceLink.GitHub | 8.0.0 | ### External Service Dependencies - **PostgreSQL**: Primary database (connection via Npgsql) - **IAM Service**: JWT token validation (Authority: `http://iam-service-net:8080` default) - **Redis**: Package referenced but not actively used in application code (health check configured) --- ## 9. Configuration ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `ASPNETCORE_ENVIRONMENT` | `Production` (Docker) / `Development` (launch) | Runtime environment | | `ASPNETCORE_URLS` | `http://+:8080` (Docker) / `http://localhost:5000` (launch) | Listening URLs | | `DATABASE_URL` | — | Fallback connection string if `ConnectionStrings:DefaultConnection` not set | ### appsettings.json Keys | Key | Default Value | Description | |-----|---------------|-------------| | `ConnectionStrings:DefaultConnection` | `Host=localhost;Port=5432;Database=mission_service;Username=postgres;Password=postgres` | PostgreSQL connection string | | `Redis:ConnectionString` | `localhost:6379` | Redis connection string | | `Jwt:Secret` | `your-super-secret-key-min-32-characters` | JWT signing key | | `Jwt:Issuer` | `goodgo-platform` | JWT issuer | | `Jwt:Audience` | `goodgo-services` | JWT audience | | `Jwt:AccessTokenExpiryMinutes` | `15` | Access token lifetime | | `Jwt:RefreshTokenExpiryDays` | `7` | Refresh token lifetime | | `Jwt:Authority` | `http://iam-service-net:8080` | OIDC authority for token validation | ### MediatR Pipeline (execution order) 1. `LoggingBehavior` — Logs request name and elapsed time (Stopwatch) 2. `ValidatorBehavior` — Runs FluentValidation validators, throws `ValidationException` on failure 3. `TransactionBehavior` — Wraps commands in a database transaction (skips queries ending in "Query"), uses `ExecutionStrategy` for retry ### Docker - Multi-stage build: `sdk:10.0` (build) -> `aspnet:10.0` (runtime) - Non-root user: `dotnetuser` (UID/GID 1001) - Health check: `curl -f http://localhost:8080/health/live` (30s interval, 3 retries, 10s start period) - Exposed port: 8080 ### Build Properties - Target: `net10.0`, C# `14.0` - Nullable reference types: enabled - Implicit usings: enabled - Treat warnings as errors: true - XML documentation generation: enabled --- ## 10. Tests ### Unit Tests (`tests/MissionService.UnitTests/`) - `Application/Commands/PerformCheckInCommandHandlerTests.cs` — Tests for the check-in command handler - `Domain/MissionAggregateTests.cs` — Tests for Mission entity behavior - `Domain/UserCheckInAggregateTests.cs` — Tests for UserCheckIn streak logic ### Functional Tests (`tests/MissionService.FunctionalTests/`) - `CustomWebApplicationFactory.cs` — Test server factory (swaps DbContext) - `Controllers/MissionsControllerTests.cs` — API endpoint integration tests --- ## 11. Notable Gaps / Observations 1. **GetUserMissionProgressQuery**: Query record is defined but has no handler implementation. 2. **No FluentValidation validators exist**: The `ValidatorBehavior` is registered but no `AbstractValidator` classes are present in the codebase. 3. **No domain events defined**: The infrastructure for dispatching domain events exists in `MissionDbContext`, but no domain event records (`INotification`) are defined or raised by any entity. 4. **No integration events**: No RabbitMQ publishers/consumers. Reward claiming does not notify external services (e.g., wallet service). 5. **UserReward aggregate**: Has a full repository and EF configuration but is not used by any command handler — rewards are tracked via `UserTask.RewardClaimed` flag only. 6. **Idempotency**: `IRequestManager`/`RequestManager` are registered but not used by any handler or behavior. 7. **Redis**: Package referenced and health check configured, but no caching logic exists in the codebase. 8. **Database name**: Default in appsettings is `mission_service` (renamed from template default). 9. **JWT audience validation**: Disabled (`ValidateAudience = false`, `ValidateIssuer = false`) — relies on IAM service for token signing.