Files
pos-system/services/mission-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f8606e0447 fix(P0): security hardening + critical bug fixes across 22 services
Wave 1 — 6 parallel agents fixing P0 issues from code audit:

Auth (18 services secured):
- Added JWT Bearer auth + [Authorize] to all unprotected controllers
- Webhook endpoints (Facebook/WhatsApp/Zalo/X) stay [AllowAnonymous]
- Health checks remain public for Docker/K8s probes
- Services: catalog, order, booking, fnb-engine, inventory, social,
  ads-manager, ads-serving, ads-billing, ads-tracking, ads-analytics,
  mkt-facebook, mkt-whatsapp, mkt-x, mkt-zalo, promotion

Template artifacts (4 services):
- mission-service: myservice_db → mission_service
- mkt-facebook: Dockerfile MyService.API → FacebookService.API
- mkt-whatsapp: MyServiceContext.cs → WhatsAppServiceContext.cs
- promotion: UserSecretsId fixed

Critical handler bugs (7 fixes):
- ads-tracking: TrackPixelEventHandler now persists to DB
- ads-tracking: RecordConversion endpoint exposed via controller
- booking: UpdateResource now applies Name + Capacity changes
- ads-manager: ListPendingAds uses correct enum (pending_review)
- mining: BanMiner calls Ban() not Suspend()
- mining: ResetMinerStreak now actually resets streak
- mkt-x: 8 missing repository DI registrations added

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:18:09 +07:00

24 KiB

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<T> 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.