Files
pos-system/services/mining-service-net/SERVICE_DOCS.md
Ho Ngoc Hai f3779c4ebe docs: add SERVICE_DOCS.md for all 24 microservices from per-service code audit
Each SERVICE_DOCS.md documents: Overview, API Endpoints, Commands, Queries,
Domain Model, Database Schema, Integration Events, Dependencies, Configuration.
Generated by 23 parallel audit agents reading actual source code.

Key corrections from audit:
- inventory-service: 12 commands/6 queries (was listed as scaffold)
- promotion-service: 12 commands/10 queries (was listed as 0)
- mission-service: 4 commands/7 queries (was listed as 0)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:54:53 +07:00

30 KiB

MiningService - Service Documentation

1. Overview

Purpose: Pi Network-style point mining service. Users ("miners") earn Mining Points (MP) over time through 24-hour mining sessions. The service manages miner profiles, mining sessions with streak bonuses, security circles (trusted groups for bonus multipliers), and referral programs.

Port: 5006 (Development, via launchSettings.json) / 8080 (Docker/Production)

Database: PostgreSQL -- mining_service on Neon PostgreSQL (ep-holy-glitter-a4hongg7-pooler.us-east-1.aws.neon.tech)

Solution: MiningService.slnx

Target Framework: .NET 10.0, C# 14

Architecture: Clean Architecture + CQRS (MediatR)

Real-time: SignalR Hub at /hubs/mining for live mining updates

Auth: JWT Bearer (Duende IdentityServer), with SignalR token via query string


2. API Endpoints

MiningController (/api/v1/mining) -- [Authorize]

Method Route Description Request Response
GET /api/v1/mining/me?userId={guid} Get current miner status Query: userId (Guid) MinerStatusDto or 404
POST /api/v1/mining/start Start a new 24h mining session Body: StartMiningRequest { UserId } StartMiningResult
POST /api/v1/mining/claim Claim mining reward from completed session Body: ClaimMiningRequest { UserId } ClaimMiningRewardResult
GET /api/v1/mining/history?userId={guid}&page=1&pageSize=20 Get mining history (paginated) Query params MiningHistoryDto
GET /api/v1/mining/rate?userId={guid} Get current mining rate breakdown Query: userId (Guid) MiningRateDto or 404
GET /api/v1/mining/leaderboard?limit=100 Get top miners leaderboard Query: limit (int) LeaderboardDto -- [AllowAnonymous]

CirclesController (/api/v1/circles) -- [Authorize]

Method Route Description Request Response
GET /api/v1/circles/me?userId={guid} Get user's circle Query: userId (Guid) CircleDto or 404
POST /api/v1/circles Create a new security circle Body: CreateCircleRequest { UserId, Name } CreateCircleResult (201)
POST /api/v1/circles/invite Invite a member to circle Body: InviteMemberRequest { UserId, TargetMinerId } 200 message
POST /api/v1/circles/accept/{inviteId}?userId={guid} Accept circle invitation Path: inviteId, Query: userId AcceptCircleInviteResult
DELETE /api/v1/circles/members/{memberId}?ownerId={guid} Remove member from circle Path: memberId, Query: ownerId RemoveCircleMemberResult
GET /api/v1/circles/trust-score?userId={guid} Get circle trust score Query: userId (Guid) CircleTrustScoreDto or 404

ReferralsController (/api/v1/referrals) -- [Authorize]

Method Route Description Request Response
GET /api/v1/referrals?userId={guid} Get referral code and referrals list Query: userId (Guid) ReferralsDto
POST /api/v1/referrals/apply Apply a referral code Body: ApplyReferralRequest { UserId, ReferralCode } ApplyReferralResult
GET /api/v1/referrals/code?userId={guid} Get my referral code Query: userId (Guid) ReferralCodeDto or 404
GET /api/v1/referrals/stats?userId={guid} Get referral statistics Query: userId (Guid) ReferralStatsDto or 404

AdminController (/api/v1/admin) -- [Authorize(Roles = "Admin")]

Configuration

Method Route Description Request Response
GET /api/v1/admin/config Get all system configuration -- SystemConfigDto
PUT /api/v1/admin/config Update system configuration Body: UpdateSystemConfigCommand UpdateConfigResult
GET /api/v1/admin/config/mining Get mining configuration -- MiningConfigDto
PUT /api/v1/admin/config/mining Update mining configuration Body: UpdateMiningConfigCommand UpdateConfigResult
GET /api/v1/admin/config/streak Get streak configuration -- StreakConfigDto
PUT /api/v1/admin/config/streak Update streak configuration Body: UpdateStreakConfigCommand UpdateConfigResult
GET /api/v1/admin/config/referral Get referral configuration -- ReferralConfigDto
PUT /api/v1/admin/config/referral Update referral configuration Body: UpdateReferralConfigCommand UpdateConfigResult

Miner Management

Method Route Description Request Response
GET /api/v1/admin/miners?page=1&pageSize=20&search= List all miners (paginated) Query params MinersListDto
GET /api/v1/admin/miners/{id} Get miner details Path: id (Guid) MinerDetailsDto or 404
PUT /api/v1/admin/miners/{minerId}/suspend Suspend a miner account Body: SuspendRequest { Reason } 200 message
PUT /api/v1/admin/miners/{minerId}/ban Ban a miner account Body: BanRequest { Reason } BanMinerResult
PUT /api/v1/admin/miners/{minerId}/restore Restore a suspended miner -- 200 message
PUT /api/v1/admin/miners/{minerId}/adjust-points Adjust miner points Body: AdjustPointsRequest { Amount, Reason } AdjustPointsResult
PUT /api/v1/admin/miners/{minerId}/reset-streak Reset miner streak Body: ResetStreakRequest { Reason } ResetStreakResult

Analytics

Method Route Description Response
GET /api/v1/admin/analytics/overview Admin dashboard overview AdminOverviewDto
GET /api/v1/admin/analytics/miners Miner analytics MinerAnalyticsDto
GET /api/v1/admin/analytics/circles Circle analytics CircleAnalyticsDto (stub)
GET /api/v1/admin/analytics/referrals Referral analytics ReferralAnalyticsDto (stub)
GET /api/v1/admin/analytics/points Points analytics PointsAnalyticsDto (stub -- no handler)
GET /api/v1/admin/analytics/streaks Streak analytics StreakAnalyticsDto (stub -- no handler)
GET /api/v1/admin/audit-logs?page=1&pageSize=50 Audit logs AuditLogsDto (stub -- no handler)

Health Endpoints (no auth)

Route Description
/health Full health check (includes PostgreSQL)
/health/live Liveness probe (app is running)
/health/ready Readiness probe (ready for traffic)

SignalR Hub

Endpoint /hubs/mining
JoinMinerGroup(minerId) Subscribe to personal mining updates
LeaveMinerGroup(minerId) Unsubscribe from personal mining updates
JoinLeaderboardGroup() Subscribe to leaderboard updates

Server-to-client messages via IMiningHubService:

  • PointsUpdated -- { earnedPoints, totalPoints, streakDays }
  • SessionStarted -- { endTime, hourlyRate }
  • StreakMilestone -- { streakDays, bonusPoints }

3. Commands

Core Commands

Command Parameters Result Handler
StartMiningCommand UserId (Guid) StartMiningResult { SessionId, HourlyRate, EndTime, StreakDays } Validates miner active, no existing session. Recalculates rate, creates MiningSession.
ClaimMiningRewardCommand UserId (Guid) ClaimMiningRewardResult { PointsEarned, TotalPoints, StreakDays, StreakBonus } Validates session ready to claim. Calculates points, increments streak, adds milestone bonuses, creates MiningHistory entries.
CreateCircleCommand UserId (Guid), Name (string) CreateCircleResult { CircleId, Name } Validates miner exists, not already in/owning a circle. Creates Circle with owner as first member.
InviteToCircleCommand UserId (Guid), TargetMinerId (Guid) bool Validates inviter owns a circle, target not in a circle. Adds member, recalculates rates if circle becomes valid.
ApplyReferralCodeCommand UserId (Guid), ReferralCode (string) ApplyReferralResult { ReferralId, ReferrerId, IsActive } Validates no existing referrer, valid code, not self-referral. Creates inactive Referral (pending KYC).

Circle Management Commands

Command Parameters Result Handler
AcceptCircleInviteCommand UserId (Guid), InviteId (Guid) AcceptCircleInviteResult { Success, Message } Simplified: adds user as member to circle by InviteId.
RemoveCircleMemberCommand OwnerId (Guid), MemberId (Guid) RemoveCircleMemberResult { Success, Message } Validates owner owns circle. Calls circle.RemoveMember().

Admin Commands

Command Parameters Result Handler
SuspendMinerCommand MinerId (Guid), Reason (string) bool Calls miner.Suspend().
RestoreMinerCommand MinerId (Guid) bool Calls miner.Restore().
BanMinerCommand MinerId (Guid), Reason (string) BanMinerResult { Success, Message } Calls miner.Suspend() (note: uses Suspend, not Ban).
AdjustMinerPointsCommand MinerId (Guid), Amount (decimal), Reason (string) AdjustPointsResult { Success, NewBalance, Message } Calls miner.AddBonusPoints().
ResetMinerStreakCommand MinerId (Guid), Reason (string) ResetStreakResult { Success, Message } Stub -- saves but does not actually reset streak.
UpdateSystemConfigCommand Mining?, Streak?, Referral? UpdateConfigResult { Success, Message } Stub -- returns success without persisting.
UpdateMiningConfigCommand BaseRate?, SessionDurationHours?, MaxSessionsPerDay? UpdateConfigResult No dedicated handler (uses UpdateSystemConfigCommandHandler pattern).
UpdateStreakConfigCommand Tiers?, GracePeriodDays? UpdateConfigResult No dedicated handler registered.
UpdateReferralConfigCommand BonusPerReferral?, MaxBonusCap?, KycRequired? UpdateConfigResult Stub handler -- returns success without persisting.

4. Queries

Query Parameters Returns
GetMinerStatusQuery UserId (Guid) MinerStatusDto? { MinerId, Role, TotalMinedPoints, HourlyRate, DailyRate, CurrentStreak, LongestStreak, StreakBonus, HasActiveSession, SessionEndTime, Status }
GetMiningHistoryQuery UserId, Page, PageSize MiningHistoryDto { Items[], TotalCount, Page, PageSize } -- in-memory pagination from miner's histories
GetMiningRateQuery UserId (Guid) MiningRateDto? { BaseRate, RoleMultiplier, CircleBonus, ReferralBonus, StreakBonus, TotalRate, HourlyPoints, DailyPoints }
GetLeaderboardQuery Limit (int, default 100) LeaderboardDto { Entries[] } -- top miners by TotalMinedPoints
GetCircleQuery UserId (Guid) CircleDto? { CircleId, Name, OwnerId, MemberCount, TrustScore, BonusMultiplier, IsValid, Status, Members[] }
GetCircleTrustScoreQuery UserId (Guid) CircleTrustScoreDto? { CircleId, Name, MemberCount, TrustScore, IsValid, BonusMultiplier }
GetReferralsQuery UserId (Guid) ReferralsDto { MyReferralCode, TotalReferrals, ActiveReferrals, TotalBonusPercent, Referrals[] }
GetReferralCodeQuery UserId (Guid) ReferralCodeDto? { ReferralCode, ShareUrl } -- URL: https://goodgo.app/invite/{code}
GetReferralStatsQuery UserId (Guid) ReferralStatsDto? { ReferralCode, TotalReferrals, ActiveReferrals, PendingReferrals, TotalEarned, CurrentBonusRate }
GetSystemConfigQuery -- SystemConfigDto { Mining, Streak, Referral } -- loads from in-memory ConfigurationRepository
GetReferralConfigQuery -- ReferralConfigDto { BonusPerReferral, MaxBonusCap, KycRequired }
GetAdminOverviewQuery -- AdminOverviewDto { TotalMiners, ActiveMiners, MinersWithActiveSession, TotalPointsMined, TotalCircles, ValidCircles, TotalReferrals, ActiveReferrals } -- uses DbContext directly
GetMinersListQuery Page, PageSize, Search? MinersListDto { Items[], TotalCount, Page, PageSize }
GetMinerDetailsQuery MinerId (Guid) MinerDetailsDto? -- full miner detail including rate, streak, circle
GetMinerAnalyticsQuery -- MinerAnalyticsDto { TotalMiners, ActiveMiners, SuspendedMiners, TotalPointsMined, PointsMinedToday, RoleDistribution }
GetCircleAnalyticsQuery -- CircleAnalyticsDto -- stub, returns zeros
GetReferralAnalyticsQuery -- ReferralAnalyticsDto -- stub, returns zeros
GetPointsAnalyticsQuery -- PointsAnalyticsDto -- no handler registered
GetStreakAnalyticsQuery -- StreakAnalyticsDto -- no handler registered
GetAuditLogsQuery Page, PageSize AuditLogsDto -- no handler registered

5. Domain Model

Aggregates

Miner (Aggregate Root)

  • File: MiningService.Domain/AggregatesModel/MinerAggregate/Miner.cs
  • Properties: UserId, Role (MinerRole), TotalMinedPoints, CurrentRate (MiningRate, owned), ActiveSession (MiningSession?, owned), Streak (MiningStreak, owned), ReferralCode, ReferredById?, CircleId?, Status (MinerStatus), CreatedAt, UpdatedAt, RowVersion (concurrency token), MiningHistories (collection)
  • Factory: Miner.Create(userId, referralCode?, referredById?) -- generates 8-char alphanumeric referral code, raises MinerCreatedDomainEvent
  • Behavior Methods:
    • StartMiningSession(baseRate, sessionHours) -- validates active status, no existing session; recalculates rate; creates session; raises MiningSessionStartedDomainEvent
    • ClaimMiningReward() -- validates session ready; calculates points + streak milestone bonuses; increments streak; creates MiningHistory entries; raises PointsMinedDomainEvent
    • RecalculateRate(baseRate, circleBonus, referralBonus) -- computes TotalRate formula
    • UpgradeRole(newRole) -- only allows upgrades
    • JoinCircle(circleId) -- auto-upgrades Pioneer to Contributor
    • LeaveCircle() -- auto-downgrades Contributor to Pioneer
    • Suspend(), Restore(), Ban() -- status transitions
    • AddBonusPoints(points, source), DeductPoints(points, reason) -- with history tracking

Milestone Bonuses (on streak claim):

Streak Day Bonus MP
7 50
14 100
30 300
60 500
90 1000

Circle (Aggregate Root)

  • File: MiningService.Domain/AggregatesModel/CircleAggregate/Circle.cs
  • Properties: OwnerId, Name, TrustScore (0-100), BonusMultiplier, Status (CircleStatus), CreatedAt, UpdatedAt, Members (collection of CircleMember)
  • Constants: MinMembers = 3, MaxMembers = 5, ValidCircleBonus = 0.25m (25%)
  • Computed: ActiveMemberCount, IsValid (Active status + 3+ active members)
  • Factory: Circle.Create(ownerId, name) -- owner auto-added as first member, status = Incomplete
  • Behavior Methods:
    • AddMember(minerId) -- validates not disbanded, max 5, not duplicate; recalculates status; raises CircleCompletedDomainEvent when reaching 3 members
    • RemoveMember(minerId) -- cannot remove owner; deactivates member
    • Disband() -- sets Disbanded, zeroes bonus, deactivates all members
  • Trust Score: 3 members = 60, 4 = 80, 5 = 100

CircleMember (Entity)

  • File: MiningService.Domain/AggregatesModel/CircleAggregate/CircleMember.cs
  • Properties: CircleId, MinerId, JoinedAt, IsActive
  • Methods: Deactivate(), Activate()

Referral (Aggregate Root)

  • File: MiningService.Domain/AggregatesModel/ReferralAggregate/Referral.cs
  • Properties: ReferrerId, ReferredId, ReferralCode, BonusRate (default 0.25 = 25%), IsActive, Level (default 1), CreatedAt, ActivatedAt?
  • Factory: Referral.Create(referrerId, referredId, referralCode, bonusRate, level) -- validates no self-referral, starts inactive
  • Behavior Methods:
    • Activate() -- sets active + timestamp; raises ReferralActivatedDomainEvent
    • Deactivate() -- sets inactive
    • CalculateBonus(baseRate) -- returns baseRate * BonusRate if active, else 0

MiningHistory (Entity, child of Miner)

  • File: MiningService.Domain/AggregatesModel/MinerAggregate/MiningHistory.cs
  • Properties: MinerId, PointsEarned, Source, SessionId?, EarnedAt, HourlyRateSnapshot, StreakDaySnapshot
  • Factories: CreateFromSession(...), CreateFromBonus(...)

Configuration Aggregates (in-memory, not persisted to DB)

MiningConfiguration

  • Properties: BaseRate (0.25), SessionDurationHours (24), MaxSessionsPerDay (1), IsGloballyEnabled (true), UpdatedAt, UpdatedBy

StreakConfiguration

  • Properties: Tiers (collection of StreakTier), GracePeriodEnabled (true), GracePeriodHours (24), RecoveryCost (50 MP), FreezeTokenDays (7), UpdatedAt, UpdatedBy
  • Default Tiers:
MinDays MaxDays BonusPercent BadgeName MilestoneMpBonus
1 2 0% -- 0
3 6 10% 3-day badge 0
7 13 25% 7-day badge 50
14 29 50% 14-day badge 100
30 59 100% 30-day badge 300
60 89 125% 60-day badge 500
90 MAX 150% 90-day badge 1000

ReferralConfiguration

  • Properties: BonusPercentPerReferral (0.25), MaxBonusPercent (1.0 = 100%), KycRequired (true), MaxReferralLevels (1), UpdatedAt, UpdatedBy

Value Objects

MiningRate (record)

  • BaseRate, RoleBonus, CircleBonus, ReferralBonus, StreakBonus
  • TotalRate = BaseRate * (1 + Role) * (1 + Circle) * (1 + Referral) * (1 + Streak)
  • DailyRate = TotalRate * 24
  • Default: BaseRate = 0.25 MP/hour

MiningStreak (record)

  • CurrentStreak, LongestStreak, LastMiningDate, FreezeTokens, IsGracePeriod
  • BonusMultiplier: 0% (days 1-2), 10% (3-6), 25% (7-13), 50% (14-29), 100% (30-59), 125% (60-89), 150% (90+)
  • IncrementStreak() -- earns 1 freeze token per 7 days
  • Reset() -- zeroes CurrentStreak

MiningSession (record)

  • SessionId, StartTime, EndTime, HourlyRate, Status (MiningSessionStatus), AccumulatedPoints
  • IsReadyToClaim -- Active status and EndTime passed
  • CalculateEarnedPoints() -- elapsed hours * HourlyRate, capped at 24h
  • MarkAsClaimed(earnedPoints) -- sets Claimed status

Enumerations

MinerRole

Value Name Bonus
0 Pioneer 0% (base user)
1 Contributor +10% (has valid circle)
2 Ambassador +25% (5+ referrals)
3 NodeOperator +50% (runs node software)

MinerStatus

Value Name
0 Active
1 Suspended
2 Banned

MiningSessionStatus

Value Name
0 Active
1 Completed
2 Claimed
3 Expired

CircleStatus

Value Name
0 Incomplete (< 3 members)
1 Active (3-5 members, valid for bonus)
2 Disbanded

Domain Exceptions

  • MiningDomainException -- base exception
  • MinerNotFoundException -- miner not found by ID or UserId
  • CircleDomainException -- circle operation failures
  • ReferralDomainException -- referral operation failures

6. Database Schema

Database: mining_service (PostgreSQL)

Migration: 20260117103924_InitialCreate

Table: Miners

Column Type Constraints
Id uuid PK
UserId uuid NOT NULL, UNIQUE (IX_Miners_UserId)
Role varchar(20) NOT NULL, string conversion of MinerRole enum
TotalMinedPoints numeric(18,4) NOT NULL
CurrentRate_BaseRate numeric(18,4) NOT NULL (owned type)
CurrentRate_RoleBonus numeric(5,4) NOT NULL (owned type)
CurrentRate_CircleBonus numeric(5,4) NOT NULL (owned type)
CurrentRate_ReferralBonus numeric(5,4) NOT NULL (owned type)
CurrentRate_StreakBonus numeric(5,4) NOT NULL (owned type)
ActiveSession_SessionId uuid NULLABLE (owned type)
ActiveSession_StartTime timestamp with time zone NULLABLE
ActiveSession_EndTime timestamp with time zone NULLABLE
ActiveSession_HourlyRate numeric(18,4) NULLABLE
ActiveSession_Status varchar(20) NULLABLE
ActiveSession_AccumulatedPoints numeric(18,4) NULLABLE
Streak_CurrentStreak integer NOT NULL (owned type)
Streak_LongestStreak integer NOT NULL
Streak_LastMiningDate timestamp with time zone NOT NULL
Streak_FreezeTokens integer NOT NULL
Streak_IsGracePeriod boolean NOT NULL
ReferralCode varchar(10) NOT NULL, UNIQUE (IX_Miners_ReferralCode)
ReferredById uuid NULLABLE
CircleId uuid NULLABLE
Status varchar(20) NOT NULL
CreatedAt timestamp with time zone NOT NULL
UpdatedAt timestamp with time zone NOT NULL
RowVersion bytea NOT NULL, row version (concurrency token)

Indexes: IX_Miners_UserId (unique), IX_Miners_ReferralCode (unique)

Table: MiningHistories

Column Type Constraints
Id uuid PK
MinerId uuid NOT NULL, FK -> Miners (CASCADE)
PointsEarned numeric(18,4) NOT NULL
Source varchar(50) NOT NULL
SessionId uuid NULLABLE
EarnedAt timestamp with time zone NOT NULL
HourlyRateSnapshot numeric(18,4) NOT NULL
StreakDaySnapshot integer NOT NULL

Indexes: IX_MiningHistories_MinerId_EarnedAt (composite)

Table: Circles

Column Type Constraints
Id uuid PK
OwnerId uuid NOT NULL
Name varchar(100) NOT NULL
TrustScore numeric(5,2) NOT NULL
BonusMultiplier numeric(5,4) NOT NULL
Status varchar(20) NOT NULL
CreatedAt timestamp with time zone NOT NULL
UpdatedAt timestamp with time zone NOT NULL

Indexes: IX_Circles_OwnerId

Table: CircleMembers

Column Type Constraints
Id uuid PK
CircleId uuid NOT NULL, FK -> Circles (CASCADE)
MinerId uuid NOT NULL
JoinedAt timestamp with time zone NOT NULL
IsActive boolean NOT NULL

Indexes: IX_CircleMembers_CircleId_MinerId (unique composite), IX_CircleMembers_MinerId

Table: Referrals

Column Type Constraints
Id uuid PK
ReferrerId uuid NOT NULL
ReferredId uuid NOT NULL, UNIQUE (IX_Referrals_ReferredId)
ReferralCode varchar(10) NOT NULL
BonusRate numeric(5,4) NOT NULL
IsActive boolean NOT NULL
Level integer NOT NULL
CreatedAt timestamp with time zone NOT NULL
ActivatedAt timestamp with time zone NULLABLE

Indexes: IX_Referrals_ReferrerId, IX_Referrals_ReferredId (unique)

Note: Table names use PascalCase (e.g., Miners, MiningHistories), not the platform-standard snake_case. Value objects (MiningRate, MiningStreak, MiningSession) are stored as owned types with {OwnerProperty}_{ValueProperty} column naming.


7. Integration Events

Published Events (outbound)

Event Fields Description
PointsMinedIntegrationEvent EventId, OccurredOn, UserId, MinerId, Points, Source, StreakDays Points mined, should be credited to wallet
ReferralActivatedIntegrationEvent EventId, OccurredOn, ReferrerId, ReferredUserId, BonusRate Referral activated after KYC
CircleCompletedIntegrationEvent EventId, OccurredOn, CircleId, OwnerId, MemberCount, BonusMultiplier Security circle completed (3+ members)

Consumed Events (inbound)

Event Handler Description
UserRegisteredIntegrationEvent UserRegisteredIntegrationEventHandler Creates new Miner profile when user registers in IAM. If referral code provided, creates Referral relationship.
UserKycCompletedIntegrationEvent UserKycCompletedIntegrationEventHandler Activates pending referral for user who completed KYC verification.

Note: Integration events are defined as records implementing IIntegrationEvent. Handlers use MediatR notification wrappers (UserRegisteredNotification, UserKycCompletedNotification). The actual message broker (RabbitMQ) integration for publishing/consuming is not yet wired up -- the event definitions and handlers exist but the transport layer is missing.

Domain Events

Event Fields Raised By
MinerCreatedDomainEvent MinerId, UserId Miner.Create()
MiningSessionStartedDomainEvent MinerId, SessionId, HourlyRate Miner.StartMiningSession()
PointsMinedDomainEvent MinerId, PointsEarned, TotalPoints, StreakDays Miner.ClaimMiningReward()
StreakUpdatedDomainEvent MinerId, PreviousStreak, NewStreak, NewBonusMultiplier Defined but not raised in current code
CircleCompletedDomainEvent CircleId, OwnerId, MemberCount Circle.RecalculateStatus() (when reaching 3 members)
ReferralActivatedDomainEvent ReferralId, ReferrerId, ReferredId Referral.Activate()
ConfigurationUpdatedDomainEvent ConfigType, UpdatedBy, UpdatedAt Defined but not raised in current code

8. Dependencies

NuGet Packages

API Layer (MiningService.API.csproj):

Package Version
MediatR 12.4.1
FluentValidation 11.11.0
FluentValidation.DependencyInjectionExtensions 11.11.0
Microsoft.AspNetCore.Authentication.JwtBearer 10.0.2
Microsoft.EntityFrameworkCore.Design 10.0.2
Microsoft.Extensions.Http.Polly 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

Infrastructure Layer (MiningService.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

Domain Layer (MiningService.Domain.csproj):

Package Version
MediatR.Contracts 2.0.1

External Service Dependencies

Service Default URL Client Interface Purpose
IAM Service http://iam-service-net:8080 IIamServiceClient Get user info, validate user, KYC status
Wallet Service http://wallet-service-net:8080 IWalletServiceClient Transfer mined points to wallet, check balance
Social Service http://social-service-net:8080 ISocialServiceClient Friend suggestions for circles, friendship checks

All external clients use Polly resilience policies:

  • Retry: 3 retries with exponential backoff (2s, 4s, 8s)
  • Circuit Breaker: Opens after 5 failures, stays open for 30s

9. Configuration

Environment Variables / appsettings.json

Key Default Description
ConnectionStrings:DefaultConnection Neon PostgreSQL connection string Primary database connection
DATABASE_URL -- Fallback database connection string
Redis:ConnectionString localhost:6379 Redis connection (declared but not actively used in repositories)
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 Token expiry (declared, not used internally)
Jwt:RefreshTokenExpiryDays 7 Refresh token expiry (declared, not used internally)
ExternalServices:IamService:BaseUrl http://iam-service-net:8080 IAM service URL
ExternalServices:WalletService:BaseUrl http://wallet-service-net:8080 Wallet service URL
ExternalServices:SocialService:BaseUrl http://social-service-net:8080 Social service URL
AllowedOrigins ["http://localhost:3000", "http://localhost:5173"] CORS allowed origins
ASPNETCORE_ENVIRONMENT Development Environment name
ASPNETCORE_URLS http://+:8080 (Docker) Listen URLs

MediatR Pipeline Order

  1. LoggingBehavior -- logs request name, elapsed time
  2. ValidatorBehavior -- runs FluentValidation (no validators currently defined)
  3. TransactionBehavior -- wraps Commands in DB transaction (skips Queries by name suffix)

Docker

  • Multi-stage build: sdk:10.0 (build) -> aspnet:10.0 (runtime)
  • Non-root user: dotnetuser (UID/GID 1001)
  • Port: 8080
  • Health check: curl -f http://localhost:8080/health/live (30s interval, 3 retries)

Database Startup

  • Auto-applies EF Core migrations on startup (dbContext.Database.MigrateAsync())
  • Npgsql retry on failure: 5 retries, 30s max delay

Idempotency

  • ClientRequest entity and RequestManager are defined but not wired into the command pipeline (no commands use idempotency checks).

Known Gaps / Stubs

  • No FluentValidation validators are defined for any command
  • ConfigurationRepository is purely in-memory (static fields with lock); database persistence for configs is not implemented
  • Analytics queries for Points, Streaks, and AuditLogs have no handlers
  • BanMinerCommand handler calls Suspend() instead of Ban()
  • ResetMinerStreakCommand handler does not actually reset the streak
  • UpdateSystemConfigCommand and UpdateMiningConfigCommand handlers do not persist changes
  • RabbitMQ integration for publishing/consuming integration events is not connected
  • Redis is declared as a dependency but not used in any repository or service
  • Dapper is referenced but no raw SQL queries exist
  • Table names use PascalCase instead of the platform-standard snake_case