# Plan 2: Website API (czappstudio.com)

## Overview

Laravel API for the CZ App Studio website providing:
- License activation with domain-locking (1 production + 1 development)
- Update checking and download management
- Release file management
- Download tracking and analytics

**Location:** `/Volumes/Storage/Projects/CZ/website/`

---

## Existing Database Tables (Keep/Modify)

### `product_versions` (EXISTS - REFACTOR)

Remove file storage fields, keep only version metadata:

- `version`, `changelog`, `release_notes`, `released_at`
- `min_laravel_version`, `min_php_version`
- `is_latest`, `is_beta`

**REMOVE these columns** (files will be in `product_version_files`):

- ~~`file_path`~~
- ~~`file_size`~~
- ~~`file_hash`~~

### `license_keys` (EXISTS - Core licensing)

Already has:

- `license_key` - Unique key (CZ-XXXX-XXXX-XXXX-XXXX)
- `license_type` - 'lifetime' or 'annual'
- `support_expires_at` - NULL for lifetime, date for annual
- `renewal_count`, `last_renewed_at`
- `user_id`, `product_id`, `order_id`

**Usage:** Use existing `canDownloadUpdates()` method in LicenseKey model.

---

## New Database Tables (To Create)

### `license_activations` (NEW - Domain Tracking)

Track which domains are activated per license (1 production + 1 development).

```php
Schema::create('license_activations', function (Blueprint $table) {
    $table->id();
    $table->foreignId('license_key_id')->constrained()->cascadeOnDelete();
    $table->string('domain');
    $table->string('domain_type');      // 'production' or 'development'
    $table->string('ip_address')->nullable();
    $table->string('server_hostname')->nullable();
    $table->string('app_version')->nullable();
    $table->string('php_version')->nullable();
    $table->string('laravel_version')->nullable();
    $table->timestamp('activated_at');
    $table->timestamp('last_validated_at')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamps();

    $table->unique(['license_key_id', 'domain']);
    $table->index(['license_key_id', 'is_active']);
});
```

### `product_version_files` (NEW - All files for versions)

Single source of truth for all downloadable files.

```php
Schema::create('product_version_files', function (Blueprint $table) {
    $table->id();
    $table->foreignId('product_version_id')->constrained()->cascadeOnDelete();
    $table->string('file_type');        // 'full', 'update', 'source' (for mobile)
    $table->string('platform')->nullable(); // 'backend', 'android', 'ios', null
    $table->string('file_name');
    $table->string('file_path');
    $table->unsignedBigInteger('file_size');
    $table->string('file_hash', 64);    // SHA256
    $table->string('from_version')->nullable(); // For update packages (e.g., "5.0.0")
    $table->json('manifest')->nullable(); // UPDATE_MANIFEST.json content
    $table->unsignedInteger('download_count')->default(0);
    $table->timestamps();

    $table->index(['product_version_id', 'file_type']);
    $table->unique(['product_version_id', 'file_type', 'from_version', 'platform']);
});
```

### `download_logs` (NEW - Download tracking)

Audit trail for downloads.

```php
Schema::create('download_logs', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->foreignId('license_key_id')->nullable()->constrained()->nullOnDelete();
    $table->foreignId('product_version_file_id')->constrained()->cascadeOnDelete();
    $table->string('ip_address')->nullable();
    $table->string('user_agent')->nullable();
    $table->timestamp('downloaded_at');

    $table->index(['license_key_id', 'downloaded_at']);
});
```

---

## Modifications to Existing Tables

### Refactor `product_versions` migration:

```php
// Remove these columns from the original migration file:
// $table->string('file_path')->nullable();
// $table->unsignedBigInteger('file_size')->nullable();
// $table->string('file_hash', 64)->nullable();
```

### Add to `license_keys` table:

```php
// Add to existing migration:
$table->unsignedTinyInteger('max_production_activations')->default(1);
$table->unsignedTinyInteger('max_development_activations')->default(1);
```

### Add to `products` table:

```php
// Add to existing migration:
$table->string('uid')->nullable()->unique()->after('alias');
// For addons: 'payroll', 'attendance', etc. - matches module.json alias
```

---

## API Endpoints

**Base URL:** `https://czappstudio.com/api/v1`

### Authentication

All API requests require:

- `X-License-Key: CZ-XXXX-XXXX-XXXX-XXXX`
- `X-Domain: customer-domain.com`

### Activation Endpoints

#### `POST /activate`

Activate a license for a domain.

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "customer-app.com",
  "domain_type": "production",
  "server_info": {
    "ip": "192.168.1.1",
    "hostname": "server1",
    "php_version": "8.2.0",
    "app_version": "5.0.1"
  }
}
```

**Response (Success):**

```json
{
  "success": true,
  "activation": {
    "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
    "product": "Open Core Business Suite",
    "license_type": "lifetime",
    "support_expires_at": null,
    "activated_domains": [
      { "domain": "customer-app.com", "type": "production" },
      { "domain": "localhost", "type": "development" }
    ],
    "addons": [
      { "uid": "payroll", "name": "Payroll", "version": "1.0.3" },
      { "uid": "attendance", "name": "Attendance", "version": "2.0.0" }
    ]
  }
}
```

**Response (Error - Domain Limit):**

```json
{
  "success": false,
  "error": "domain_limit_exceeded",
  "message": "This license already has 1 production domain activated. Please deactivate the existing domain first.",
  "current_domains": [{ "domain": "old-domain.com", "type": "production" }]
}
```

#### `POST /deactivate`

Deactivate a domain from license.

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "old-domain.com"
}
```

#### `POST /validate`

Check if activation is still valid (periodic check).

**Request:**

```json
{
  "license_key": "CZ-XXXX-XXXX-XXXX-XXXX",
  "domain": "customer-app.com",
  "app_version": "5.0.1"
}
```

**Response:**

```json
{
  "valid": true,
  "license_type": "lifetime",
  "support_active": true,
  "support_expires_at": null,
  "latest_version": "5.0.2",
  "update_available": true,
  "addons": [...]
}
```

### Update Check Endpoints

#### `GET /updates/check`

Check for available updates.

**Request Headers:**

```
X-License-Key: CZ-XXXX-XXXX-XXXX-XXXX
X-Domain: customer-app.com
X-Current-Version: 5.0.1
```

**Response:**

```json
{
  "update_available": true,
  "current_version": "5.0.1",
  "latest_version": "5.0.2",
  "can_update": true,
  "update_type": "patch",
  "changelog": "### Bug Fixes\n- Fixed payroll calculation...",
  "update_package": {
    "type": "differential",
    "from_version": "5.0.1",
    "to_version": "5.0.2",
    "file_size": 2457600,
    "checksum": "sha256:abc123..."
  },
  "modules_with_updates": [{ "uid": "payroll", "current": "1.0.2", "latest": "1.0.3" }]
}
```

#### `GET /updates/check-addons`

Check addon updates.

**Response:**

```json
{
  "addons": [
    {
      "uid": "payroll",
      "name": "Payroll",
      "current_version": "1.0.2",
      "latest_version": "1.0.3",
      "update_available": true,
      "can_update": true,
      "changelog": "..."
    }
  ]
}
```

### Download Endpoints

#### `GET /downloads/{product_id}/latest`

Get download URL for latest version.

#### `GET /downloads/{product_id}/version/{version}`

Get specific version.

#### `GET /downloads/{product_id}/update/{from_version}`

Get update package from specific version to latest.

**Response:**

```json
{
  "download_url": "https://czappstudio.com/downloads/temp/abc123.zip",
  "expires_at": "2025-01-08T12:00:00Z",
  "file_name": "opencorebs-update-5.0.1-to-5.0.2.zip",
  "file_size": 2457600,
  "checksum_sha256": "abc123..."
}
```

### Release Management Endpoints (Admin)

#### `POST /admin/releases`

Create new release (called by release-tool).

**Request:**

```json
{
  "api_key": "admin-secret-key",
  "product_id": 1,
  "version": "5.0.2",
  "changelog": "...",
  "files": [
    { "type": "full", "path": "/tmp/opencorebs-v5.0.2.zip" },
    {
      "type": "update",
      "from_version": "5.0.1",
      "path": "/tmp/update-5.0.1-to-5.0.2.zip"
    }
  ]
}
```

---

## Files to Create/Modify

### New Files

```
app/
├── Http/
│   └── Controllers/
│       └── Api/
│           └── V1/
│               ├── ActivationController.php
│               ├── UpdateController.php
│               ├── DownloadController.php
│               └── ReleaseController.php
├── Services/
│   └── Activation/
│       ├── ActivationService.php
│       ├── DomainValidator.php
│       └── UpdateService.php
└── Models/
    ├── ProductVersionFile.php
    ├── LicenseActivation.php
    └── DownloadLog.php

routes/
└── api.php                         # Add API routes

database/migrations/
├── xxxx_create_product_version_files_table.php
├── xxxx_create_license_activations_table.php
├── xxxx_create_download_logs_table.php
├── xxxx_add_activation_limits_to_license_keys_table.php
├── xxxx_add_uid_to_products_table.php
└── xxxx_remove_file_columns_from_product_versions_table.php
```

### Filament Admin Resources

```
app/Filament/Admin/Resources/
├── ProductVersionFileResource.php      # Manage downloadable files
├── LicenseActivationResource.php       # View activations
├── DownloadLogResource.php             # View download logs
└── ProductResource.php                 # Modify - add file management relation
```

### Modify Existing Files

```
app/Models/LicenseKey.php              # Add activations relationship, activation methods
app/Models/ProductVersion.php          # Add files relationship, remove file accessors
app/Services/License/LicenseKeyService.php  # Add activation methods
routes/api.php                         # Add API routes
config/services.php                    # Add API config
```

---

## Implementation Phases

### Phase 1: Database Schema

1. Create migration to add `uid` column to `products` table
2. Create migration for `license_activations` table
3. Create migration for `product_version_files` table
4. Create migration for `download_logs` table
5. Create migration to add activation limits to `license_keys` table
6. Create migration to remove file columns from `product_versions` table

### Phase 2: Models & Relationships

1. Create `LicenseActivation` model with relationships
2. Create `ProductVersionFile` model with relationships
3. Create `DownloadLog` model with relationships
4. Update `LicenseKey` model with activations relationship
5. Update `ProductVersion` model with files relationship

### Phase 3: Services

1. Create `ActivationService` with activate/deactivate/validate methods
2. Create `DomainValidator` for domain limit checking
3. Create `UpdateService` for update checking logic
4. Update `LicenseKeyService` with new activation methods

### Phase 4: API Controllers

1. Create `ActivationController` (POST /activate, /deactivate, /validate)
2. Create `UpdateController` (GET /updates/check, /updates/check-addons)
3. Create `DownloadController` (GET /downloads/*)
4. Create `ReleaseController` (POST /admin/releases)
5. Add API routes with proper middleware

### Phase 5: Filament Admin

1. Create `LicenseActivationResource` for viewing activations
2. Create `ProductVersionFileResource` for file management
3. Create `DownloadLogResource` for viewing download logs
4. Update `ProductVersionResource` with file management relation manager

---

## Verification

### API Test Commands (curl)

```bash
# Activate license
curl -X POST https://czappstudio.com/api/v1/activate \
  -H "Content-Type: application/json" \
  -d '{"license_key": "CZ-TEST-1234", "domain": "test.com", "domain_type": "production"}'

# Validate license
curl -X POST https://czappstudio.com/api/v1/validate \
  -H "X-License-Key: CZ-TEST-1234" \
  -H "X-Domain: test.com"

# Check updates
curl -X GET https://czappstudio.com/api/v1/updates/check \
  -H "X-License-Key: CZ-TEST-1234" \
  -H "X-Domain: test.com" \
  -H "X-Current-Version: 5.0.1"

# Deactivate domain
curl -X POST https://czappstudio.com/api/v1/deactivate \
  -H "Content-Type: application/json" \
  -d '{"license_key": "CZ-TEST-1234", "domain": "test.com"}'
```

### Test Scenarios

1. **Activation**: New license → Activate domain → Returns success with license info
2. **Domain Limits**: Try activating 2nd production domain → Should fail with clear error
3. **Deactivation**: Deactivate domain → License freed → Can activate new domain
4. **Validation**: Valid license → Returns valid=true with support status
5. **Expired Support**: Annual license expired → valid=true but support_active=false
6. **Update Check**: Version behind → Returns update_available=true with package info
7. **Download**: Valid support → Returns signed download URL
8. **Download (Expired)**: Expired support → Returns 403 with renew message

### Verification Checklist

- [ ] `POST /activate` creates activation record
- [ ] Domain limits enforced (1 prod + 1 dev per license)
- [ ] `POST /deactivate` removes activation record
- [ ] `POST /validate` updates last_validated_at timestamp
- [ ] `GET /updates/check` returns correct update info
- [ ] Download URLs are signed and expire after 1 hour
- [ ] Download logs are created for each download
- [ ] Filament admin shows activations per license
- [ ] Filament admin allows file upload to versions
