# CLAUDE.md - Project Context for AI Assistants

## Project Overview

**RDM RSP API** - A .NET 8.0 Web API for taxpayer services (Revenue Data Management - Revenue Service Provider).

- **Framework:** ASP.NET Core 8.0 Minimal APIs
- **Database:** SQL Server (Entity Framework Core)
- **Deployment:** Docker on Linux (Ubuntu)
- **Production URL:** https://rdmapi.eirs.gov.ng
- **Git Repository:** https://gitlab.com/blouza-tech/rdm-rsp-api.git

## Project Structure

```
rdm-rsp-api/
├── Program.cs              # Main entry point, all API endpoints defined here
├── ApiDbContext.cs         # EF Core database context (large file, ~320KB)
├── FormModel.cs            # Request models and validators
├── ResponseModel.cs        # Response DTOs for stored procedure results
├── Repository/             # Data access layer
├── Model/                  # EF Core entity models
├── ErasModel/              # Additional entity models
├── Dockerfile              # Multi-stage Docker build
├── docker-compose.yml      # Production deployment config
├── appsettings.json        # Application configuration
└── .env                    # Environment variables (not in git)
```

## Key Technical Details

### Database Connection
- Connection string configured via environment variable: `ConnectionStrings__DefaultConnection`
- Docker passes this via: `DB_CONNECTION` in `.env` file
- **Important:** Always use DI-injected `ApiDbContext`, never `new ApiDbContext()`

### API Endpoints Pattern
Most endpoints use repository pattern with dependency injection:
```csharp
app.MapGet("/endpoint", ([FromServices] IRepository repository) => { ... });
```

Some endpoints call stored procedures directly:
```csharp
app.MapPost("/endpoint", ([FromBody] Model fm, [FromServices] ApiDbContext context) => {
    context.DbSet.FromSqlRaw("EXEC sp_name @param = @param", params).ToList();
});
```

### Stored Procedure Calls - Best Practices
When calling stored procedures:
1. **Use explicit named parameters:** `@Param = @Param` not positional
2. **Use `DBNull.Value` for NULL:** Not `SqlInt32.Null`
3. **Parse types correctly:** `int.Parse()` for integer parameters
4. **Use DI context:** `[FromServices] ApiDbContext context`

Example:
```csharp
var parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("@intStatus", 1));

if (!string.IsNullOrEmpty(fm.TaxYear))
    parameters.Add(new SqlParameter("@TaxYear", int.Parse(fm.TaxYear)));
else
    parameters.Add(new SqlParameter("@TaxYear", DBNull.Value));

return context.ResponseModels.FromSqlRaw(
    "EXEC usp_StoredProc @Param1 = @Param1, @Param2 = @Param2",
    parameters.ToArray()).ToList();
```

### Response Models - Nullable Strings
All string properties in response models that map to stored procedure results **must be nullable** (`string?`) to handle NULL values from the database:
```csharp
public class ResponseModel
{
    public int? MDAServiceID { get; set; }
    public string? MDAServiceCode { get; set; }  // Must be nullable!
    // ...
}
```

## Deployment

### Production Server
- **Host:** `137.184.192.64` (web-app-01)
- **SSH:** `ssh -i ~/.ssh/do_eirs_prod root@137.184.192.64`
- **Path:** `/var/www/rdmapi.eirs.gov.ng`
- **URL:** https://rdmapi.eirs.gov.ng
- **Port:** 8086 (mapped to container port 8080)
- **Docker command:** `docker compose` (not `docker-compose`)

### Staging Server
- **Host:** `45.13.59.80`
- **SSH (password):** `ssh timothy@45.13.59.80` (password: `timothy@123`)
- **SSH (key):** `ssh -i ~/.ssh/id_ed25519 timothy@45.13.59.80`
- **Path:** `/var/www/eirs-projects/rdmapi.blouzatech.ng`

### Deployment Commands
```bash
# Pull latest code
git pull origin main

# Rebuild and deploy (use --no-cache to ensure fresh build)
docker compose build --no-cache && docker compose up -d --force-recreate

# Check logs
docker logs rdm-rsp-api --tail=100

# Check container status
docker ps
```

### Common Deployment Issues
1. **Cached Docker layers:** Use `--no-cache` flag when rebuilding
2. **Old container running:** Use `--force-recreate` flag
3. **Permission denied on scripts:** Run with `bash script.sh` or `chmod +x`

## Recent Changes Log

### 2026-03-09: Fix GetMDAServiceListFilteredByMdaName Endpoint
**Issue:** Endpoint returned empty array despite stored procedure returning data.

**Root Causes & Fixes:**
1. **Wrong NULL type:** Changed `SqlInt32.Null` to `DBNull.Value`
2. **Type mismatch:** Added `int.Parse()` for numeric parameters
3. **Parameter order:** Changed to explicit named parameter binding
4. **DI bypass:** Changed `new ApiDbContext()` to `[FromServices] ApiDbContext`
5. **Non-nullable strings:** Made all `ResponseModel` string properties nullable

**Files Modified:**
- `Program.cs` (lines 151-180)
- `ResponseModel.cs` (lines 20-41)

## Testing

### Test Endpoint Locally
```bash
curl -X POST https://rdmapi.eirs.gov.ng/GetMDAServiceListFilteredByMdaName \
  -H "Content-Type: application/json" \
  -d '{"mdaServiceID":"","taxYear":"2026","mdaServiceName":"","mdaName":"Edo Internal Revenue Service"}'
```

### Test Stored Procedure in SSMS
```sql
EXEC usp_GetMDAServiceList_Filter_By_MDA_Name
    @MDAServiceID = NULL,
    @TaxYear = 2026,
    @MDAServiceName = NULL,
    @intStatus = 1,
    @MDAName = 'Edo Internal Revenue Service'
```

## Common Issues & Solutions

| Issue | Cause | Solution |
|-------|-------|----------|
| Empty response `[]` | Wrong DB connection | Use DI-injected context |
| `SqlNullValueException` | Non-nullable string property | Make property nullable (`string?`) |
| Parameter mismatch | Positional parameters | Use explicit named binding |
| Docker using old code | Cached layers | Use `--no-cache` flag |
| Container not updated | Old container running | Use `--force-recreate` flag |

## Environment Variables

Required in `.env` file:
```
DB_CONNECTION=Server=...;Database=EIRS;User Id=...;Password=...;TrustServerCertificate=True
API_PORT=8086
ASPNETCORE_ENVIRONMENT=Production
TZ=Africa/Lagos
```
