# Local Development: User Secrets Configuration

This guide explains how to safely manage secrets locally without storing them in version control.

## Overview

- **Production/CI:** Secrets stored in Gitea Actions Secrets → injected as environment variables at build/deploy time
- **Local Dev:** Secrets stored in user-secrets → NOT checked into git
- **Code:** Never hardcodes secrets; reads from environment or IOptions

---

## Setup User Secrets (One-Time)

### 1. Initialize User Secrets Store

```bash
cd src/KArtSell.Host
dotnet user-secrets init
```

This creates `~/.microsoft/usersecrets/<PROJECT_GUID>/secrets.json` (not in git).

### 2. Store Secrets Locally

```powershell
# PowerShell (Windows)
cd src/KArtSell.Host

# PostgreSQL connection string
dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"

# KRX API Key
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here"

# OpenDart API Key (optional)
dotnet user-secrets set "ExternalApis:OpenDart:ApiKey" "your-opendart-key-here"

# KIS API Keys (optional)
dotnet user-secrets set "ExternalApis:Kis:ApiKey" "your-kis-api-key"
dotnet user-secrets set "ExternalApis:Kis:SecretKey" "your-kis-secret-key"
```

**Bash/macOS:**
```bash
cd src/KArtSell.Host

dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-krx-api-key-here"
```

### 3. Verify Secrets Are Set

```bash
cd src/KArtSell.Host
dotnet user-secrets list
```

**Expected Output:**
```
ConnectionStrings:Postgres = Host=localhost;Port=5432;...
ExternalApis:KrxOpenApi:ApiKey = your-krx-api-key-here
ExternalApis:OpenDart:ApiKey = your-opendart-key-here
ExternalApis:Kis:ApiKey = your-kis-api-key
ExternalApis:Kis:SecretKey = your-kis-secret-key
```

---

## Build Issues & Solutions

### .NET SDK Version Mismatch

**Problem:** `global.json` requires .NET 10.0.100 GA, but only preview version installed
```
Requested SDK version: 10.0.100
Install the [10.0.100] .NET SDK or update global.json to match an installed SDK.
```

**Solution:** Use NuGet.config to resolve package source conflicts
```bash
# NuGet.config at project root handles Telerik source override
# (Telerik was configured in .sln but not actually used in code)
# This prevents NU1507 "warning-as-error" during restore
```

The project includes `NuGet.config` which:
- Configures only nuget.org as package source
- Removes transitive Telerik source (build-only artifact)
- Works with both GA and preview .NET 10 SDKs

### Building Locally

```bash
cd C:\Job_Roomz\KArtSell.Aegis

# Release build (optimized binaries)
dotnet build KArtSell.sln -c Release

# Development mode (with appsettings.Development.json)
$env:ASPNETCORE_ENVIRONMENT = "Development"
$env:KARTSELL_POSTGRES = "Host=127.0.0.1;Port=5432;Database=kartselldb_test;Username=kartsell_test;Password=kartsell4321@!_test"
$env:KRX_OPENAPI = "stub-key-for-testing"

dotnet run --project src/KArtSell.Host -c Release --no-build
```

Host listens on: `http://127.0.0.1:5002`

---

## How It Works

### Development (dotnet run)
```
User Secrets → appsettings.json (placeholder) → Program.cs (ResolveSecret)
     ↓              ↓                                      ↓
(highest           (if ${VAR})                      (merged together)
priority)
```

When you run `dotnet run`, ASP.NET Core:
1. Loads appsettings.json (has `${KARTSELL_POSTGRES}` placeholders)
2. Overlays user-secrets (if in Development)
3. Overlays environment variables (highest priority)

Result: `Program.cs` sees actual values, not placeholders.

### CI/CD (Gitea Actions)
```
Gitea Secrets (env injection) → appsettings.json → Program.cs
      ↓                               ↓                ↓
${{ secrets.* }}            (placeholder)      (resolved to actual)
```

Gitea Actions:
1. Sets `KARTSELL_POSTGRES` and `KRX_API_KEY` as environment variables
2. Code reads from environment (highest priority in ResolveSecret)
3. Never stores secrets in build artifacts

---

## Verify Setup Works

### 1. Start PostgreSQL (SSH Tunnel)

```bash
ssh -L 5432:127.0.0.1:5432 kjh2064@178.104.200.7
```

Keep this running in a separate terminal.

### 2. Run Application

```bash
cd src/KArtSell.Host
dotnet run -c Release
```

**Expected:**
- Application starts without "KARTSELL_POSTGRES is required" error
- Logs show database connection successful
- Hangfire dashboard accessible at http://localhost:5000/hangfire

### 3. Verify API Works

```bash
curl http://localhost:5000/health
# Expected: 200 OK
```

---

## Troubleshooting

### Issue: "ConnectionStrings:Postgres is required"

**Cause:** User secrets not set or not loaded

**Fix:**
```bash
# Check if secrets are set
dotnet user-secrets list

# If empty, re-set them
dotnet user-secrets set "ConnectionStrings:Postgres" "Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell"

# If using different terminal, make sure you're in src/KArtSell.Host directory
```

### Issue: "KRX_API_KEY is required"

**Cause:** API key not configured

**Fix:**
```bash
# Set KRX API key
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "your-api-key"

# Or set via environment variable (overrides user-secrets)
$env:KRX_API_KEY = "your-api-key"  # PowerShell
export KRX_API_KEY="your-api-key"  # Bash
```

### Issue: Secrets Showing in Logs

**Never should happen** — ResolveSecret does not log secret values.

If you see secrets in logs:
1. Check application doesn't log Configuration
2. Check Serilog is not in Verbose mode
3. Report as security issue

---

## Best Practices

### ✅ DO

- Store secrets in user-secrets locally
- Use environment variables in CI/CD (via Gitea Secrets)
- Commit **only** appsettings.json with placeholders
- Keep `.gitignore` excluding `secrets.json`
- Rotate API keys quarterly

### ❌ DON'T

- Commit secrets to git (even accidentally)
- Store credentials in appsettings.Development.json
- Commit `.env` files
- Log secrets in any log level
- Share API keys via chat/email

---

## Adding New Secrets

When adding a new API (e.g., new data provider):

1. **Add to ExternalApiOptions.cs:**
   ```csharp
   public class NewProviderSettings
   {
       public string ApiKey { get; set; } = string.Empty;
       public string BaseUrl { get; set; } = "https://api.provider.com";
   }
   ```

2. **Add to appsettings.json:**
   ```json
   "ExternalApis": {
     "NewProvider": {
       "ApiKey": "${NEW_PROVIDER_API_KEY}",
       "BaseUrl": "https://api.provider.com"
     }
   }
   ```

3. **Set locally:**
   ```bash
   dotnet user-secrets set "ExternalApis:NewProvider:ApiKey" "your-key"
   ```

4. **Add to Gitea Secrets:**
   - Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
   - Click "+ New Secret"
   - Name: `NEW_PROVIDER_API_KEY`
   - Value: actual key

5. **Add to CI/CD workflow:**
   ```yaml
   env:
     NEW_PROVIDER_API_KEY: ${{ secrets.NEW_PROVIDER_API_KEY }}
   ```

---

## Rotating Secrets

### Local Secrets

```bash
cd src/KArtSell.Host

# Update the secret
dotnet user-secrets set "ExternalApis:KrxOpenApi:ApiKey" "new-api-key"

# Restart application
# (no need to commit, secrets are local)
```

### Production Secrets (Gitea)

1. Go to: https://gitea.taxbaik.com/kjh2064/KArtSell.Aegis/settings/actions/secrets
2. Click on secret → "Update"
3. Enter new value
4. Save
5. Next CI/CD run uses new secret automatically

---

## See Also

- `docs/CLAUDE.md` — Project instructions and architecture
- `GATE_3_EXECUTION_GUIDE.md` — Setting up Gate 3 shadow run (uses same secrets)
- `.gitea/workflows/secrets-injection.yml` — CI/CD workflow with secret injection
