# MPS-17: REST API v1 — HMAC-signed product/content creation + file upload

## Purpose

Enable CI/CD pipelines (e.g. permacomputer.com) to programmatically:
- Create products (fiat/crypto priced) or content (free)
- Upload files directly to Spaces via presigned POST
- Confirm uploads and get CDN URLs

## Auth — HMAC public/private key pairs

Each shop has API key pairs. A key pair is:
- `public_key` — `mps_pub_{32 hex}` — identifies the pair, safe to log
- `secret_key` — `mps_sec_{64 hex}` — signs requests, shown **once** on creation

No bearer tokens. The secret never travels over the wire. Every request is
signed with HMAC-SHA256. Replay window: ±300 seconds.

### Signing scheme

```
string_to_sign = "{METHOD}\n{PATH}\n{TIMESTAMP}\n{SHA256_OF_BODY_HEX}"
signature      = hmac_sha256(secret_key, string_to_sign).hexdigest()

Request headers:
  X-MPS-Key:       mps_pub_abc123...
  X-MPS-Timestamp: 1712345678
  X-MPS-Signature: sha256=abcdef...
```

### Shell example (for CI)

```bash
METHOD=POST
PATH=/api/v1/products
TIMESTAMP=$(date +%s)
BODY='{"title":"Debian permacomputer","description":"...","price":"0.00"}'
BODY_HASH=$(echo -n "$BODY" | sha256sum | awk '{print $1}')
STRING_TO_SIGN="${METHOD}\n${PATH}\n${TIMESTAMP}\n${BODY_HASH}"
SIG=$(echo -n "$STRING_TO_SIGN" | openssl dgst -sha256 -hmac "$MPS_SECRET_KEY" | awk '{print $2}')

curl -X POST https://my.makepostsell.com/api/v1/products \
  -H "X-MPS-Key: $MPS_PUBLIC_KEY" \
  -H "X-MPS-Timestamp: $TIMESTAMP" \
  -H "X-MPS-Signature: sha256=$SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"
```

## API Endpoints

```
POST /api/v1/products                     create product (is_sellable=True)
POST /api/v1/content                      create content (is_sellable=False)
GET  /api/v1/products/{id}                get product
GET  /api/v1/content/{id}                 get content
POST /api/v1/products/{id}/upload-url     get presigned POST URL for file upload
POST /api/v1/content/{id}/upload-url      get presigned POST URL for file upload
POST /api/v1/products/{id}/files/confirm  confirm S3 upload, register file, get CDN URL
POST /api/v1/content/{id}/files/confirm   confirm S3 upload, register file, get CDN URL
```

### POST /api/v1/products

Request:
```json
{
  "title":       "Debian permacomputer 6.12 amd64",
  "description": "Debian bookworm with CWE-407 patched Linux 6.12 kernel",
  "price":       "9.99",
  "visibility":  "public"
}
```

Response 201:
```json
{
  "id":       "abc123...",
  "url":      "https://my.makepostsell.com/p/abc123/debian-permacomputer",
  "edit_url": "https://my.makepostsell.com/p/abc123/edit"
}
```

### POST /api/v1/content

Same body minus `price`. Response 201 same shape with `/c/` URL.

### POST /api/v1/products/{id}/upload-url

Request:
```json
{
  "filename":     "debian-permacomputer-6.12-amd64.qcow2",
  "content_type": "application/octet-stream"
}
```

Response 200:
```json
{
  "upload_url":   "https://nyc3.digitaloceanspaces.com/...",
  "fields":       { "key": "...", "AWSAccessKeyId": "...", ... },
  "confirm_path": "/api/v1/products/{id}/files/confirm",
  "key":          "products/shop_id/product_id/product.qcow2"
}
```

### POST /api/v1/products/{id}/files/confirm

Request:
```json
{
  "key":      "products/shop_id/product_id/product.qcow2",
  "filename": "debian-permacomputer-6.12-amd64.qcow2"
}
```

Response 200:
```json
{
  "cdn_url":  "https://plan-period-files.nyc3.cdn.digitaloceanspaces.com/..."
}
```

## Shop Settings UI

New section at bottom of `/s/{shop_id}/settings`:
- List active key pairs (label, public key, created date, last used)
- "Generate new key pair" form (label input)
- Secret shown **once** in a flash-style `<output>` element after generation
- Per-key revoke button

Routes:
```
POST /s/{shop_id}/api-keys/generate
POST /s/{shop_id}/api-keys/{key_id}/revoke
```

## Files

| File | Change |
|------|--------|
| `models/api_key.py` | New: `MpsApiKey` model |
| `models/__init__.py` | Import `MpsApiKey` |
| `models/meta.py` | Add `MpsApiKey` to `CLASS_TO_TABLE` |
| `views/api/__init__.py` | HMAC auth: `api_key_required` decorator |
| `views/api/items.py` | All API endpoints |
| `routes.py` | Add API + key management routes |
| `views/shop.py` | `api_keys_generate`, `api_key_revoke` handlers |
| `templates/shop_settings.j2` | API Keys section |
| `alembic/versions/` | Migration: `mps_api_key` table |
| `tests/test_models.py` | `MpsApiKey` unit tests |
| `tests/test_integration.py` | HMAC signing integration tests |
| `tests/test_functional.py` | API endpoint functional tests |
