# Dashboard API Client Layer

**Version**: 1.0.0
**Architecture**: Domain-Driven Design (DDD)
**Pattern**: Repository Pattern with Mapper Layer

## Table of Contents

1. [Overview](#overview)
2. [Architecture](#architecture)
3. [API Modules](#api-modules)
4. [Usage Examples](#usage-examples)
5. [Error Handling](#error-handling)
6. [Type Safety](#type-safety)
7. [Migration Guide](#migration-guide)
8. [Best Practices](#best-practices)

---

## Overview

The Dashboard API Client Layer provides a type-safe, domain-driven interface for communicating with the Archer API backend. The UI layer works exclusively with **domain objects** (entities and value objects), while the API client layer handles all schema mapping internally.

### Key Principles

1. **Domain-Driven**: UI works with domain objects (Tenant, User, Company, etc.)
2. **Schema Isolation**: UI never imports from `@archer/api-interface/schemas`
3. **Internal Mapping**: Request/response schema transformations are internal implementation details
4. **Type Safety**: End-to-end type safety with TypeScript domain objects
5. **Single Responsibility**: Each API module handles one domain concept

### Architecture Benefits

- **Separation of Concerns**: Clear boundaries between UI, API client, and HTTP layers
- **Type Safety**: Compile-time validation of API calls via domain types
- **Maintainability**: Schema changes don't ripple through UI code
- **Testability**: Easy to mock domain objects for UI tests
- **Consistency**: Unified error handling and response transformation

---

## Architecture

### Data Flow Pattern

```
UI Layer (React Components)
    ↓
Domain Objects (Tenant, User, Company, etc.)
    ↓
API Client Methods (tenantsApi.createTenant, etc.)
    ↓
[Internal] Request Mappers (domain → schema)
    ↓
[Internal] HTTP Client (axios)
    ↓
API Backend (@archer/api)
    ↓
[Internal] HTTP Response
    ↓
[Internal] Response Mappers (schema → domain)
    ↓
Domain Objects
    ↓
UI Layer
```

### Layer Responsibilities

**UI Layer**:
- Creates/manipulates domain objects
- Calls API client methods with domain objects
- Receives domain objects from API
- Never imports from `@archer/api-interface/schemas`

**API Client Layer** (this package):
- Exposes API methods accepting/returning domain objects
- Maps domain objects to request schemas (internal)
- Maps response schemas to domain objects (internal)
- Handles HTTP communication and errors

**HTTP Layer** (ApiClient):
- Manages axios instance and configuration
- Handles auth token injection
- Transforms axios errors to ApiError
- Implements retry logic with exponential backoff

### Directory Structure

```
api/
├── auth/                      # Authentication module
│   ├── mappers/                # Request/response mappers (internal)
│   │   ├── AuthRequestMapper.ts
│   │   ├── AuthResponseMapper.ts
│   │   └── SessionResponseMapper.ts
│   ├── api.ts                  # Public API methods
│   └── index.ts                # Module exports
│
├── tenant/                    # Tenant CRUD module
│   ├── mappers/
│   │   ├── TenantRequestMapper.ts
│   │   └── TenantResponseMapper.ts
│   ├── api.ts
│   └── index.ts
│
├── company/                   # Company management module
├── user/                      # User administration module
├── integration-keys/          # API key management module
├── info-center/               # Page discovery & analysis module
├── upload/                    # File upload module
│
├── shared/                    # Shared utilities
│   ├── errors.ts               # ApiError classes and factories
│   └── types.ts                # Shared types (PaginationMeta, etc.)
│
├── index.ts                   # Main barrel exports
├── README.md                  # [THIS FILE] Documentation
└── examples.ts                # Usage examples
```

---

## API Modules

### Auth Module (`authApi`)

Authentication and session management.

**Methods**:
- `login(credentials)` - Login with email/password
- `register(data)` - Register new user
- `refreshToken(token)` - Refresh access token
- `logout()` - Logout current session
- `getMe()` - Get current user profile
- `getSessions()` - List active sessions
- `revokeSession(id)` - Revoke specific session
- `revokeAllOtherSessions()` - Revoke all except current

**Domain Objects**:
- `User` - User entity with profile information

**Example**:
```typescript
import { authApi } from '@/infrastructure/http/api';

// Login
const { user, session } = await authApi.login({
  email: 'user@example.com',
  password: 'secure-password',
});

// Get current user
const currentUser = await authApi.getMe();
```

---

### Tenant Module (`tenantsApi`)

Website integration management (tenant CRUD).

**Methods**:
- `createTenant(companyId, tenant)` - Create new tenant
- `listTenants(companyId, params?)` - List tenants with pagination
- `getTenant(id)` - Get tenant by ID
- `updateTenant(id, tenant)` - Update tenant
- `deleteTenant(id)` - Delete tenant

**Domain Objects**:
- `Tenant` - Tenant entity (domain, name, type, status)
- `Domain` - Value object for domain validation

**Example**:
```typescript
import { tenantsApi } from '@/infrastructure/http/api';
import { Tenant } from '@/domain/entities/Tenant';
import { Domain } from '@/domain/value-objects/Domain';

// Create tenant with domain object
const tenant = new Tenant({
  name: 'Acme Corp Website',
  domain: new Domain('acme.com'),
  type: 'production',
  status: 'active',
});

const created = await tenantsApi.createTenant('company-id', tenant);
```

---

### Company Module (`companiesApi`)

Company management (multi-tenancy root).

**Methods**:
- `createCompany(company)` - Create new company
- `listCompanies(params?)` - List companies with pagination
- `getCompany(id)` - Get company by ID
- `updateCompany(id, company)` - Update company
- `deleteCompany(id)` - Delete company

**Domain Objects**:
- `Company` - Company entity (name, type, status)

**Example**:
```typescript
import { companiesApi } from '@/infrastructure/http/api';
import { Company } from '@/domain/entities/Company';

const company = new Company({
  name: 'Acme Corporation',
  type: 'enterprise',
  status: 'active',
});

const created = await companiesApi.createCompany(company);
```

---

### User Module (`usersApi`)

User administration and role management.

**Methods**:
- `createUser(user)` - Create new user
- `getUser(id)` - Get user by ID
- `updateUser(id, user)` - Update user
- `deleteUser(id)` - Delete user

**Domain Objects**:
- `User` - User entity with roles and permissions

**Example**:
```typescript
import { usersApi } from '@/infrastructure/http/api';
import { User } from '@/domain/entities/User';

const user = new User({
  email: 'john@acme.com',
  firstName: 'John',
  lastName: 'Doe',
  role: 'admin',
  status: 'active',
});

const created = await usersApi.createUser(user);
```

---

### Integration Keys Module (`integrationKeysApi`)

API key management for tenant authentication.

**Methods**:
- `createKey(tenantId, key)` - Generate new API key
- `listKeys(tenantId, params?)` - List keys with pagination
- `revokeKey(id)` - Revoke API key

**Domain Objects**:
- `IntegrationKey` - Integration key entity (key hash, prefix, expiration)

**Special Note**: `createKey` returns `{ key: IntegrationKey, plaintextKey: string }` where `plaintextKey` is shown **ONCE ONLY** (never stored, never retrievable).

**Example**:
```typescript
import { integrationKeysApi } from '@/infrastructure/http/api';
import { IntegrationKey } from '@/domain/entities/IntegrationKey';

const keyData = new IntegrationKey({
  name: 'Production API Key',
  permissions: ['read', 'write'],
});

const { key, plaintextKey } = await integrationKeysApi.createKey('tenant-id', keyData);

// IMPORTANT: Show plaintextKey to user NOW - cannot retrieve later
console.log('Save this key:', plaintextKey);
```

---

### Info Center Module (`infoCenterApi`)

Page discovery and analysis (crawler integration).

**Methods**:
- `listPages(tenantId, params?)` - List discovered pages
- `getPage(id)` - Get page by ID
- `updatePage(id, page)` - Update page metadata
- `deletePage(id)` - Delete page
- `getAnalysis(pageId)` - Get page analysis
- `triggerAnalysis(pageId, params)` - Schedule initial analysis
- `retriggerAnalysis(pageId, params)` - Schedule re-analysis
- `deleteAnalysis(pageId)` - Delete analysis
- `getPageStats(tenantId)` - Get page statistics

**Domain Objects**:
- `DiscoveredPage` - Discovered page entity (URL, status, element count)
- `PageAnalysis` - Analysis entity (elements, selectors, confidence scores)

**Example**:
```typescript
import { infoCenterApi } from '@/infrastructure/http/api';

// List pages for tenant
const { pages, pagination } = await infoCenterApi.listPages('tenant-id', {
  page: 1,
  limit: 20,
  status: 'active',
});

// Get analysis with elements
const analysis = await infoCenterApi.getAnalysis('page-id');
console.log('Found elements:', analysis.elements.length);

// Trigger re-scan (creates new version)
await infoCenterApi.retriggerAnalysis('page-id', {
  priority: 'high',
  createNewVersion: true,
});
```

---

### Upload Module (`uploadApi`)

File upload with progress tracking.

**Methods**:
- `uploadFile(file, onProgress?)` - Upload file with progress callback
- `deleteFile(id)` - Delete uploaded file

**Example**:
```typescript
import { uploadApi } from '@/infrastructure/http/api';

const handleUpload = async (file: File) => {
  const result = await uploadApi.uploadFile(file, (progress) => {
    console.log(`Upload progress: ${progress.percentage}%`);
  });

  console.log('Uploaded file ID:', result.id);
  console.log('File URL:', result.url);
};
```

---

## Usage Examples

### Basic CRUD Operations

```typescript
import { tenantsApi } from '@/infrastructure/http/api';
import { Tenant } from '@/domain/entities/Tenant';
import { Domain } from '@/domain/value-objects/Domain';

// CREATE
const newTenant = new Tenant({
  name: 'Production Site',
  domain: new Domain('example.com'),
  type: 'production',
  status: 'active',
});

const created = await tenantsApi.createTenant('company-id', newTenant);

// READ (single)
const tenant = await tenantsApi.getTenant(created.id);

// READ (list with pagination)
const { tenants, pagination } = await tenantsApi.listTenants('company-id', {
  page: 1,
  limit: 20,
  sortBy: 'createdAt',
  sortOrder: 'desc',
});

// UPDATE
tenant.updateStatus('suspended');
const updated = await tenantsApi.updateTenant(tenant.id, tenant);

// DELETE
await tenantsApi.deleteTenant(tenant.id);
```

---

### Authentication Flow

```typescript
import { authApi } from '@/infrastructure/http/api';

// Login
try {
  const { user, session } = await authApi.login({
    email: 'user@example.com',
    password: 'secure-password',
  });

  // Store tokens
  localStorage.setItem('accessToken', session.accessToken);
  localStorage.setItem('refreshToken', session.refreshToken);

  console.log('Logged in as:', user.fullName);
} catch (error) {
  if (error instanceof ApiError && error.isAuthError()) {
    console.error('Invalid credentials');
  }
}

// Refresh token when expired
const newSession = await authApi.refreshToken(localStorage.getItem('refreshToken'));
localStorage.setItem('accessToken', newSession.accessToken);

// Logout
await authApi.logout();
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
```

---

### Working with Domain Objects

```typescript
import { Tenant } from '@/domain/entities/Tenant';
import { Domain } from '@/domain/value-objects/Domain';
import { tenantsApi } from '@/infrastructure/http/api';

// Create domain object with validation
const tenant = new Tenant({
  name: 'My Website',
  domain: new Domain('mysite.com'), // Domain validates format
  type: 'production',
  status: 'active',
});

// Domain methods available
tenant.activate();
tenant.suspend();
const displayName = tenant.getDisplayName();

// API call with domain object
const created = await tenantsApi.createTenant('company-id', tenant);

// Returned object is also domain entity
console.log(created.getDisplayName()); // Domain methods available
```

---

### Pagination

```typescript
import { tenantsApi } from '@/infrastructure/http/api';

const { tenants, pagination } = await tenantsApi.listTenants('company-id', {
  page: 2,
  limit: 10,
  sortBy: 'name',
  sortOrder: 'asc',
  status: 'active', // Filter by status
});

console.log('Page:', pagination.page);
console.log('Total items:', pagination.total);
console.log('Total pages:', pagination.totalPages);
console.log('Has next page:', pagination.hasNext);
```

---

## Error Handling

### ApiError Structure

All API errors are transformed to `ApiError` instances with:

```typescript
interface ApiError extends Error {
  statusCode?: number;      // HTTP status code (404, 500, etc.)
  code: ApiErrorCode;       // Categorized error code
  originalError?: unknown;  // Original axios error
  details?: unknown;        // Additional details (validation errors)
}

enum ApiErrorCode {
  NETWORK_ERROR = 'NETWORK_ERROR',
  AUTH_ERROR = 'AUTH_ERROR',
  VALIDATION_ERROR = 'VALIDATION_ERROR',
  NOT_FOUND = 'NOT_FOUND',
  FORBIDDEN = 'FORBIDDEN',
  CONFLICT = 'CONFLICT',
  SERVER_ERROR = 'SERVER_ERROR',
  TIMEOUT = 'TIMEOUT',
  UNKNOWN = 'UNKNOWN',
}
```

### Error Handling Patterns

**Basic Try-Catch**:
```typescript
import { authApi } from '@/infrastructure/http/api';
import { ApiError } from '@/infrastructure/http/api';

try {
  const { user } = await authApi.login(credentials);
  console.log('Login successful:', user.email);
} catch (error) {
  if (error instanceof ApiError) {
    console.error('API Error:', error.message);
    console.error('Status Code:', error.statusCode);
    console.error('Error Code:', error.code);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

**Type-Specific Handling**:
```typescript
try {
  const tenant = await tenantsApi.getTenant('invalid-id');
} catch (error) {
  if (error instanceof ApiError) {
    if (error.isAuthError()) {
      // Redirect to login
      window.location.href = '/login';
    } else if (error.code === ApiErrorCode.NOT_FOUND) {
      // Show not found message
      console.error('Tenant not found');
    } else if (error.isValidationError()) {
      // Show validation errors
      console.error('Validation failed:', error.details);
    } else if (error.isNetworkError()) {
      // Show offline message
      console.error('Network error - check connection');
    } else if (error.isServerError()) {
      // Show generic server error
      console.error('Server error - try again later');
    }
  }
}
```

**React Hook Pattern**:
```typescript
import { useState } from 'react';
import { tenantsApi } from '@/infrastructure/http/api';
import { ApiError, ApiErrorCode } from '@/infrastructure/http/api';

function useTenants(companyId: string) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<ApiError | null>(null);

  const loadTenants = async () => {
    setLoading(true);
    setError(null);

    try {
      const result = await tenantsApi.listTenants(companyId);
      return result;
    } catch (err) {
      if (err instanceof ApiError) {
        setError(err);
      } else {
        setError(new ApiError('Unknown error', ApiErrorCode.UNKNOWN));
      }
      throw err;
    } finally {
      setLoading(false);
    }
  };

  return { loadTenants, loading, error };
}
```

---

## Type Safety

### Compile-Time Validation

TypeScript ensures type safety at compile time:

```typescript
import { tenantsApi } from '@/infrastructure/http/api';
import { Tenant } from '@/domain/entities/Tenant';

// ✅ CORRECT: Domain object
const tenant = new Tenant({
  name: 'My Site',
  domain: new Domain('mysite.com'),
  type: 'production',
  status: 'active',
});
await tenantsApi.createTenant('company-id', tenant);

// ❌ COMPILE ERROR: Plain object not allowed
await tenantsApi.createTenant('company-id', {
  name: 'My Site',
  domain: 'mysite.com', // Type error
  type: 'production',
});

// ✅ CORRECT: Return type is domain object
const result = await tenantsApi.getTenant('id');
result.activate(); // Domain methods available
```

### Type Inference

Return types are automatically inferred:

```typescript
// Type: Promise<{ tenants: Tenant[], pagination: PaginationMeta }>
const result = await tenantsApi.listTenants('company-id');

// Type: Tenant[]
const tenants = result.tenants;

// Type: Tenant
const firstTenant = tenants[0];

// Domain methods available with autocomplete
firstTenant.activate();
firstTenant.getDisplayName();
```

---

## Migration Guide

### From localStorage to API

**Before** (localStorage repository):
```typescript
// UI component
import { useTenantRepository } from '@/infrastructure/repositories';

function TenantList() {
  const tenantRepo = useTenantRepository();
  const tenants = await tenantRepo.findAll();

  return <div>{/* render tenants */}</div>;
}
```

**After** (API client):
```typescript
// UI component
import { tenantsApi } from '@/infrastructure/http/api';

function TenantList() {
  const { tenants } = await tenantsApi.listTenants('company-id');

  return <div>{/* render tenants */}</div>;
}
```

### Migration Checklist

1. **Update imports**:
   - Replace repository imports with API imports
   - Import from `@/infrastructure/http/api`

2. **Update method calls**:
   - `repository.findAll()` → `api.list(companyId)`
   - `repository.findById(id)` → `api.get(id)`
   - `repository.save(entity)` → `api.create(entity)` or `api.update(id, entity)`
   - `repository.delete(id)` → `api.delete(id)`

3. **Handle pagination**:
   - API returns `{ items, pagination }` instead of flat array
   - Destructure response: `const { tenants, pagination } = await api.listTenants(...)`

4. **Add error handling**:
   - Wrap API calls in try-catch
   - Handle `ApiError` instances
   - Implement loading and error states

5. **Update tests**:
   - Mock API client instead of repository
   - Mock domain objects for API responses

---

## Best Practices

### 1. Always Use Domain Objects

**✅ CORRECT**:
```typescript
const tenant = new Tenant({ name: 'My Site', domain: new Domain('mysite.com'), type: 'production', status: 'active' });
await tenantsApi.createTenant('company-id', tenant);
```

**❌ INCORRECT**:
```typescript
await tenantsApi.createTenant('company-id', { name: 'My Site', domain: 'mysite.com', type: 'production' });
```

### 2. Never Import Schemas in UI

**✅ CORRECT**:
```typescript
import { tenantsApi } from '@/infrastructure/http/api';
import { Tenant } from '@/domain/entities/Tenant';
```

**❌ INCORRECT**:
```typescript
import { CreateTenantRequest } from '@archer/api-interface/schemas/request';
```

### 3. Handle Errors Explicitly

**✅ CORRECT**:
```typescript
try {
  const tenant = await tenantsApi.getTenant(id);
} catch (error) {
  if (error instanceof ApiError) {
    // Handle API error
  }
}
```

**❌ INCORRECT**:
```typescript
const tenant = await tenantsApi.getTenant(id); // Unhandled errors
```

### 4. Use Type Inference

**✅ CORRECT**:
```typescript
const result = await tenantsApi.listTenants('company-id');
// TypeScript infers types automatically
```

**❌ INCORRECT**:
```typescript
const result: any = await tenantsApi.listTenants('company-id');
// Lost type safety
```

### 5. Destructure Paginated Responses

**✅ CORRECT**:
```typescript
const { tenants, pagination } = await tenantsApi.listTenants('company-id');
```

**❌ INCORRECT**:
```typescript
const result = await tenantsApi.listTenants('company-id');
const tenants = result.tenants; // Extra variable
```

### 6. Leverage Domain Methods

**✅ CORRECT**:
```typescript
const tenant = await tenantsApi.getTenant(id);
tenant.activate(); // Use domain method
await tenantsApi.updateTenant(id, tenant);
```

**❌ INCORRECT**:
```typescript
const tenant = await tenantsApi.getTenant(id);
tenant.status = 'active'; // Direct property mutation
await tenantsApi.updateTenant(id, tenant);
```

### 7. Implement Loading States

**✅ CORRECT**:
```typescript
const [loading, setLoading] = useState(false);
const [error, setError] = useState<ApiError | null>(null);

const loadData = async () => {
  setLoading(true);
  setError(null);
  try {
    const result = await tenantsApi.listTenants('company-id');
    // Handle result
  } catch (err) {
    setError(err as ApiError);
  } finally {
    setLoading(false);
  }
};
```

### 8. Use Async/Await

**✅ CORRECT**:
```typescript
const tenant = await tenantsApi.getTenant(id);
```

**❌ INCORRECT**:
```typescript
tenantsApi.getTenant(id).then(tenant => { /* ... */ });
```

---

## Related Documentation

- **Domain Layer**: `apps/dashboard/src/domain/` - Domain entities and value objects
- **Application Layer**: `apps/dashboard/src/application/` - Use cases and mappers
- **Infrastructure Layer**: `apps/dashboard/src/infrastructure/` - Repositories and HTTP client
- **API Interface Package**: `packages/api-interface/` - Request/response schemas (internal use)
- **Architecture Guide**: `.ai/ARCHITECTURE.md` - Complete project architecture

---

## Support

For issues, questions, or contributions:

- **Internal Documentation**: Check `.ai/ARCHITECTURE.md` for project structure
- **Examples**: See `examples.ts` in this directory for complete working examples

---

**Copyright** 2025 TWWIM UG. All rights reserved. (www.twwim.com)
