# Membership Service

> Membership management service for GoodGo platform.

## Overview

The Membership Service manages member information beyond basic IAM data. It handles:

- **Member Profiles** - Membership information (country code, gender, preferences)
- **Membership Levels** - Free, Basic, Premium tier management
- **Preferences** - User preferences stored as JSON

> [!NOTE]
> Profile information like avatar, phone, address, date of birth is managed by IAM Service's UserProfile. Membership Service only stores membership-specific data.

This service receives UserId from IAM Service and stores additional membership data using the same ID for consistency.

## Prerequisites

| Requirement | Version |
|-------------|---------|
| .NET SDK | 10.0.101+ |
| Docker | 24.0+ |
| PostgreSQL | 15+ (Neon recommended) |

```bash
# Check .NET version
dotnet --version
# Should output: 10.0.xxx
```

## Quick Start

### 1. Configure Environment

```bash
# Copy environment template
cp .env.example .env

# Edit with your configuration
nano .env
```

### 2. Run with Docker Compose

```bash
# From deployments/local directory
cd deployments/local
docker-compose up -d membership-service-net

# View logs
docker-compose logs -f membership-service-net
```

### 3. Run Locally

```bash
# Navigate to service directory
cd services/membership-service-net

# Restore dependencies
dotnet restore

# Build all projects
dotnet build

# Run the API
dotnet run --project src/MembershipService.API
```

## Project Structure

```
membership-service-net/
├── src/
│   ├── MembershipService.API/           # Presentation Layer
│   │   ├── Controllers/
│   │   │   └── MembersController.cs     # Member CRUD endpoints
│   │   ├── Application/
│   │   │   ├── Commands/                # CreateMember, UpdateProfile, ChangeLevel
│   │   │   ├── Queries/                 # GetMemberById, GetMembers
│   │   │   ├── Behaviors/               # Transaction, Validation, Logging
│   │   │   └── Validations/             # FluentValidation validators
│   │   └── Program.cs                   # App entry point
│   │
│   ├── MembershipService.Domain/        # Domain Layer
│   │   ├── AggregatesModel/
│   │   │   └── MemberAggregate/
│   │   │       ├── Member.cs            # Aggregate root
│   │   │       ├── MembershipLevel.cs   # Enumeration
│   │   │       └── IMemberRepository.cs # Repository interface
│   │   ├── Events/                      # Domain events
│   │   ├── Exceptions/                  # Domain exceptions
│   │   └── SeedWork/                    # Base classes
│   │
│   └── MembershipService.Infrastructure/
│       ├── EntityConfigurations/        # EF Core configs
│       ├── ExternalServices/            # IAM Service client
│       ├── Idempotency/                 # Request deduplication
│       ├── Repositories/                # Repository implementations
│       └── MembershipServiceContext.cs  # DbContext
│
├── tests/
│   ├── MembershipService.UnitTests/
│   └── MembershipService.FunctionalTests/
│
├── docs/
│   ├── en/                              # English documentation
│   └── vi/                              # Vietnamese documentation
│
├── Dockerfile
└── docker-compose.yml
```

## API Endpoints

### Member Management

| Method | Endpoint | Description | Auth Required |
|--------|----------|-------------|---------------|
| `GET` | `/api/v1/members` | Get paginated members (with search) | Yes |
| `GET` | `/api/v1/members/{id}` | Get member by ID | Yes |
| `GET` | `/api/v1/members/me` | Get current user's profile | Yes |
| `GET` | `/api/v1/members/{id}/progress` | Get member's level progress | Yes |
| `GET` | `/api/v1/members/{id}/experience` | Get member's EXP history | Yes |
| `POST` | `/api/v1/members` | Create new member | Yes |
| `POST` | `/api/v1/members/{id}/experience` | Add experience points | Yes |
| `PUT` | `/api/v1/members/{id}` | Update member profile | Yes |

### Health Endpoints

| Endpoint | Purpose |
|----------|---------|
| `/health` | Full health status |
| `/health/live` | Liveness probe (Kubernetes) |
| `/health/ready` | Readiness probe (Kubernetes) |

### Swagger Documentation

Access Swagger UI at: `http://localhost:5002/swagger`

## Domain Model

### Member Aggregate

```csharp
public class Member : Entity, IAggregateRoot
{
    public Guid UserId => Id;           // Same as IAM Service UserId
    public string CountryCode { get; }  // ISO 3166-1 alpha-2
    public string? Gender { get; }      // male, female, other
    public MembershipLevel MembershipLevel { get; }
    public string? Preferences { get; } // JSON
    public bool IsDeleted { get; }      // Soft delete
    public DateTime CreatedAt { get; }
    public DateTime UpdatedAt { get; }
    
    public void UpdateGender(string? gender);
    public void UpdateCountryCode(string countryCode);
    public void UpdatePreferences(string? preferencesJson);
    public void ChangeMembershipLevel(MembershipLevel newLevel);
    public void Delete();
    public void Restore();
}
```

> [!NOTE]
> Profile fields like `PhoneNumber`, `AvatarUrl`, `Address`, `DateOfBirth` are managed by IAM Service's UserProfile and are not part of this Member entity.

### Membership Levels

| Level | ID | Description |
|-------|-----|-------------|
| Free | 1 | Default free tier |
| Basic | 2 | Basic paid membership |
| Premium | 3 | Premium membership |

### Domain Events

- `MemberCreatedDomainEvent` - When new member is created
- `MemberUpdatedDomainEvent` - When profile is updated
- `MembershipLevelChangedDomainEvent` - When level changes

## CQRS Pattern

### Commands

```csharp
// Create member
public class CreateMemberCommand : IRequest<CreateMemberResult>
{
    public Guid UserId { get; set; }
    public string CountryCode { get; set; } = "VN";
    public string? Gender { get; set; }
}

// Update profile
public class UpdateMemberProfileCommand : IRequest<UpdateMemberProfileResult>
{
    public Guid MemberId { get; set; }
    public string? Gender { get; set; }
    public string? CountryCode { get; set; }
    public string? Preferences { get; set; }
}

// Change membership level  
public class ChangeMembershipLevelCommand : IRequest<ChangeMembershipLevelResult>
{
    public Guid MemberId { get; set; }
    public int NewLevelId { get; set; }
}
```

### Queries

```csharp
// Get by ID
public class GetMemberByIdQuery : IRequest<MemberDto?>
{
    public Guid MemberId { get; set; }
}

// Get paginated list with search
public class GetMembersQuery : IRequest<GetMembersResult>
{
    public int PageIndex { get; set; } = 0;
    public int PageSize { get; set; } = 10;
    public string? SearchTerm { get; set; }
}
```

## Testing

```bash
# Run all tests
dotnet test

# Run unit tests only
dotnet test tests/MembershipService.UnitTests

# Run functional tests only  
dotnet test tests/MembershipService.FunctionalTests

# Run with coverage
dotnet test /p:CollectCoverage=true
```

**Test Summary:**
- Unit Tests: 12 tests (Domain Member + MembershipLevel)
- Functional Tests: 3 tests (Controller authorization)

## Configuration

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `ASPNETCORE_ENVIRONMENT` | Environment name | `Development` |
| `DATABASE_URL` | PostgreSQL connection string | - |
| `Jwt__Authority` | JWT Authority URL | - |
| `Jwt__Audience` | JWT Audience | - |

### appsettings.json

```json
{
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Database=membership;Username=postgres;Password=postgres"
  },
  "Jwt": {
    "Authority": "https://iam.goodgo.com",
    "Audience": "membership-api"
  },
  "IamService": {
    "BaseUrl": "http://iam-service-net:8080",
    "TimeoutSeconds": 30,
    "CacheDurationSeconds": 300
  }
}
```

## Database

### Run Migrations

```bash
cd services/membership-service-net

# Create new migration
dotnet ef migrations add InitialCreate -p src/MembershipService.Infrastructure -s src/MembershipService.API

# Apply migrations
dotnet ef database update -p src/MembershipService.Infrastructure -s src/MembershipService.API
```

### Database Schema

```sql
-- Members table
CREATE TABLE members (
    id UUID PRIMARY KEY,
    country_code VARCHAR(2) NOT NULL DEFAULT 'VN',
    gender VARCHAR(10),
    membership_level_id INT NOT NULL DEFAULT 1,
    preferences JSONB,
    is_deleted BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

-- Indexes
CREATE INDEX ix_members_membership_level ON members(membership_level_id);
CREATE INDEX ix_members_created_at ON members(created_at);
CREATE INDEX ix_members_is_deleted ON members(is_deleted);
```

> [!NOTE]
> Fields like `phone_number`, `avatar_url`, `address` are not in this schema. They are stored in IAM Service's UserProfile.

## Deployment

### Docker Build

```bash
# Build Docker image
docker build -t membership-service:latest .

# Run container
docker run -p 5002:8080 --env-file .env membership-service:latest
```

### Kubernetes

See [ARCHITECTURE.md](./ARCHITECTURE.md) for Kubernetes deployment manifests.

## Related Services

- **IAM Service** - Authentication, identity and UserProfile (`iam-service-net`)
- **Storage Service** - File/avatar storage (`storage-service-net`)
- **Wallet Service** - Payment and points (`wallet-service-net`)

## IAM Service Integration

Membership Service communicates with IAM Service via `IIamServiceClient`:

```csharp
public interface IIamServiceClient
{
    // User validation
    Task<IamUserInfo?> ValidateUserAsync(string accessToken);
    Task<IamUserInfo?> GetUserByIdAsync(string userId, string accessToken);
    Task<bool> UserExistsAsync(string userId, string accessToken);
    
    // Roles & Permissions
    Task<IReadOnlyList<string>> GetUserRolesAsync(string userId, string accessToken);
    Task<IReadOnlyList<string>> GetUserPermissionsAsync(string userId, string accessToken);
    Task<bool> HasPermissionAsync(string userId, string permission, string accessToken);
    Task<bool> HasRoleAsync(string userId, string role, string accessToken);
    
    // Health check
    Task<IamHealthStatus> CheckHealthAsync();
    Task<bool> IsAvailableAsync();
}
```

## Resources

- [.NET 10 Documentation](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-10)
- [DDD with .NET](https://docs.microsoft.com/en-us/dotnet/architecture/microservices/)
- [MediatR](https://github.com/jbogard/MediatR)
- [FluentValidation](https://docs.fluentvalidation.net/)

## License

Proprietary - GoodGo Platform
