# Tenant API Implementation (Tenant Management)

This document describes the implementation of the Tenant Management API endpoints for the MaplePress frontend, integrated with the MaplePress backend API.

## Overview

The Tenant API endpoints manage tenants (organizations) in the multi-tenant system. Each tenant represents an organization with its own users, resources, and isolated data. Tenants are identified by both UUID and a URL-friendly slug.

## Backend API Endpoints

### Create Tenant
**Endpoint**: `POST /api/v1/tenants`
**Authentication**: Required (JWT token)

### Get Tenant by ID
**Endpoint**: `GET /api/v1/tenants/{id}`
**Authentication**: Required (JWT token)

### Get Tenant by Slug
**Endpoint**: `GET /api/v1/tenants/slug/{slug}`
**Authentication**: Required (JWT token)

**Documentation**: `/cloud/maplepress-backend/docs/API.md` (lines 416-558)

## Request/Response Structures

### Create Tenant Request
```json
{
  "name": "TechStart Inc",
  "slug": "techstart"
}
```

### Tenant Response
```json
{
  "id": "850e8400-e29b-41d4-a716-446655440000",
  "name": "TechStart Inc",
  "slug": "techstart",
  "status": "active",
  "created_at": "2024-10-24T00:00:00Z",
  "updated_at": "2024-10-24T00:00:00Z"
}
```

## Frontend Implementation

### TenantService (`src/services/API/TenantService.js`)

Handles all tenant management operations with the backend API.

**Key Features:**
- Create new tenants
- Retrieve tenant by ID or slug
- Client-side validation (name and slug)
- Slug generation helper
- Response transformation (snake_case to camelCase)
- User-friendly error message mapping

**Methods:**

#### `createTenant(tenantData)`
Create a new tenant (organization).

```javascript
import TenantService from './services/API/TenantService';

const tenant = await TenantService.createTenant({
  name: "TechStart Inc",
  slug: "techstart"
});

console.log(tenant);
// Output:
// {
//   id: "850e8400-...",
//   name: "TechStart Inc",
//   slug: "techstart",
//   status: "active",
//   createdAt: Date object
// }
```

**Parameters:**
- `tenantData.name` (string, required): Tenant/organization name
- `tenantData.slug` (string, required): URL-friendly identifier (lowercase, hyphens only)

**Returns:**
```javascript
{
  id: string,          // Tenant ID (UUID)
  name: string,        // Tenant name
  slug: string,        // Tenant slug
  status: string,      // Tenant status (e.g., "active")
  createdAt: Date      // Creation timestamp
}
```

**Throws:**
- "Tenant data is required" - If tenantData is missing
- "Tenant name is required" - If name is missing
- "Tenant slug is required" - If slug is missing
- "Tenant slug must contain only lowercase letters, numbers, and hyphens" - Invalid slug format
- "Tenant slug already exists. Please choose a different slug." - Slug conflict (409)
- "Authentication required. Please log in to continue." - Missing/invalid token

#### `getTenantById(tenantId)`
Retrieve tenant information by ID.

```javascript
const tenant = await TenantService.getTenantById("850e8400-...");
console.log(tenant.name); // "TechStart Inc"
```

**Parameters:**
- `tenantId` (string, required): Tenant ID (UUID format)

**Returns:**
```javascript
{
  id: string,
  name: string,
  slug: string,
  status: string,
  createdAt: Date,
  updatedAt: Date
}
```

**Throws:**
- "Tenant ID is required" - If ID is missing
- "Invalid tenant ID format" - If ID is not a valid UUID
- "Tenant not found." - If tenant doesn't exist (404)
- "Authentication required. Please log in to continue." - Missing/invalid token

#### `getTenantBySlug(slug)`
Retrieve tenant information by slug.

```javascript
const tenant = await TenantService.getTenantBySlug("techstart");
console.log(tenant.id); // "850e8400-..."
```

**Parameters:**
- `slug` (string, required): Tenant slug

**Returns:**
```javascript
{
  id: string,
  name: string,
  slug: string,
  status: string,
  createdAt: Date,
  updatedAt: Date
}
```

**Throws:**
- "Tenant slug is required" - If slug is missing
- "Tenant slug cannot be empty" - If slug is empty after trimming
- "Tenant not found." - If tenant doesn't exist (404)
- "Authentication required. Please log in to continue." - Missing/invalid token

#### `generateSlug(name)`
Generate a URL-friendly slug from a tenant name.

```javascript
const slug = TenantService.generateSlug("TechStart Inc!");
console.log(slug); // "techstart-inc"

const slug2 = TenantService.generateSlug("My   Company");
console.log(slug2); // "my-company"
```

**Parameters:**
- `name` (string): Tenant name

**Returns:** `string` - Generated slug (lowercase, hyphens, alphanumeric only)

**Transformation Rules:**
- Converts to lowercase
- Replaces spaces with hyphens
- Removes special characters
- Removes consecutive hyphens
- Removes leading/trailing hyphens

#### `validateSlug(slug)`
Validate a tenant slug format.

```javascript
const result = TenantService.validateSlug("techstart");
console.log(result); // { valid: true, error: null }

const invalid = TenantService.validateSlug("Tech Start!");
console.log(invalid); // { valid: false, error: "Slug must contain..." }
```

**Parameters:**
- `slug` (string): Slug to validate

**Returns:**
```javascript
{
  valid: boolean,      // true if slug is valid
  error: string|null   // Error message if invalid, null if valid
}
```

**Validation Rules:**
- Required (non-empty)
- Length: 2-50 characters
- Format: lowercase letters, numbers, hyphens only
- Cannot start or end with hyphen
- Cannot contain consecutive hyphens

#### `validateName(name)`
Validate a tenant name.

```javascript
const result = TenantService.validateName("TechStart Inc");
console.log(result); // { valid: true, error: null }

const invalid = TenantService.validateName("A");
console.log(invalid); // { valid: false, error: "Name must be at least 2 characters" }
```

**Parameters:**
- `name` (string): Name to validate

**Returns:**
```javascript
{
  valid: boolean,
  error: string|null
}
```

**Validation Rules:**
- Required (non-empty)
- Length: 2-100 characters

## Data Flow

### Create Tenant Flow
```
User provides tenant name and slug
    ↓
TenantService.createTenant()
    ↓
Validate name and slug (client-side)
    ↓
ApiClient.post() with JWT token
    ↓
Token automatically refreshed if needed
    ↓
POST /api/v1/tenants
    ↓
Backend validates (slug uniqueness, format)
    ↓
Backend creates tenant in database
    ↓
Backend returns tenant data
    ↓
TenantService transforms response
    ↓
Component receives tenant data
```

### Get Tenant Flow
```
User requests tenant (by ID or slug)
    ↓
TenantService.getTenantById() or getTenantBySlug()
    ↓
Validate input format
    ↓
ApiClient.get() with JWT token
    ↓
Token automatically refreshed if needed
    ↓
GET /api/v1/tenants/{id} or /api/v1/tenants/slug/{slug}
    ↓
Backend retrieves tenant from database
    ↓
Backend returns tenant data
    ↓
TenantService transforms response
    ↓
Component receives tenant data
```

## Error Handling

### Error Types

| Error Condition | Response | Frontend Behavior |
|----------------|----------|-------------------|
| Missing authentication | 401 Unauthorized | "Authentication required. Please log in to continue." |
| Invalid tenant data | 400 Bad Request | Specific validation error |
| Slug already exists | 409 Conflict | "Tenant slug already exists. Please choose a different slug." |
| Tenant not found | 404 Not Found | "Tenant not found." |
| Server error | 500 Internal Server Error | Generic error message |

### Error Message Mapping

```javascript
// Backend error → Frontend error
"unauthorized" → "Authentication required. Please log in to continue."
"conflict" / "already exists" → "Tenant slug already exists. Please choose a different slug."
"not found" → "Tenant not found."
"slug" → "Invalid tenant slug. Must contain only lowercase letters, numbers, and hyphens."
"name" → "Invalid tenant name provided."
```

## Validation Rules

### Tenant Name
- **Required**: Cannot be empty
- **Length**: 2-100 characters
- **Format**: Any printable characters allowed

### Tenant Slug
- **Required**: Cannot be empty
- **Length**: 2-50 characters
- **Format**: Lowercase letters, numbers, hyphens only
- **Pattern**: `^[a-z0-9-]+$`
- **Restrictions**:
  - Cannot start or end with hyphen
  - Cannot contain consecutive hyphens
  - Must be URL-safe

## Usage Examples

### Create a New Tenant

```javascript
import React, { useState } from 'react';
import TenantService from '../../services/API/TenantService';

function CreateTenantForm() {
  const [name, setName] = useState('');
  const [slug, setSlug] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  // Auto-generate slug from name
  const handleNameChange = (e) => {
    const newName = e.target.value;
    setName(newName);

    // Generate slug automatically
    const generatedSlug = TenantService.generateSlug(newName);
    setSlug(generatedSlug);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError('');
    setLoading(true);

    // Validate before sending
    const nameValidation = TenantService.validateName(name);
    if (!nameValidation.valid) {
      setError(nameValidation.error);
      setLoading(false);
      return;
    }

    const slugValidation = TenantService.validateSlug(slug);
    if (!slugValidation.valid) {
      setError(slugValidation.error);
      setLoading(false);
      return;
    }

    try {
      const tenant = await TenantService.createTenant({ name, slug });
      console.log("Tenant created:", tenant);
      // Redirect or show success message
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Tenant Name:</label>
        <input
          type="text"
          value={name}
          onChange={handleNameChange}
          placeholder="TechStart Inc"
          maxLength={100}
        />
      </div>

      <div>
        <label>Tenant Slug:</label>
        <input
          type="text"
          value={slug}
          onChange={(e) => setSlug(e.target.value.toLowerCase())}
          placeholder="techstart"
          maxLength={50}
        />
        <small>URL: /tenants/{slug}</small>
      </div>

      {error && <p className="error">{error}</p>}

      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Tenant'}
      </button>
    </form>
  );
}

export default CreateTenantForm;
```

### Display Tenant Information

```javascript
import React, { useEffect, useState } from 'react';
import TenantService from '../../services/API/TenantService';

function TenantProfile({ tenantId }) {
  const [tenant, setTenant] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchTenant = async () => {
      try {
        const data = await TenantService.getTenantById(tenantId);
        setTenant(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchTenant();
  }, [tenantId]);

  if (loading) return <div>Loading tenant...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!tenant) return null;

  return (
    <div className="tenant-profile">
      <h2>{tenant.name}</h2>
      <p>Slug: {tenant.slug}</p>
      <p>Status: {tenant.status}</p>
      <p>Created: {tenant.createdAt.toLocaleDateString()}</p>
      <p>Updated: {tenant.updatedAt.toLocaleDateString()}</p>
    </div>
  );
}

export default TenantProfile;
```

### Search Tenant by Slug

```javascript
import TenantService from '../../services/API/TenantService';

function TenantLookup() {
  const [slug, setSlug] = useState('');
  const [tenant, setTenant] = useState(null);

  const handleSearch = async () => {
    try {
      const data = await TenantService.getTenantBySlug(slug);
      setTenant(data);
    } catch (error) {
      console.error("Tenant not found:", error.message);
      setTenant(null);
    }
  };

  return (
    <div>
      <input
        value={slug}
        onChange={(e) => setSlug(e.target.value)}
        placeholder="Enter tenant slug"
      />
      <button onClick={handleSearch}>Search</button>

      {tenant && (
        <div>
          <h3>{tenant.name}</h3>
          <p>ID: {tenant.id}</p>
        </div>
      )}
    </div>
  );
}
```

### Validate Slug in Real-Time

```javascript
import React, { useState, useEffect } from 'react';
import TenantService from '../../services/API/TenantService';

function SlugInput({ value, onChange }) {
  const [validation, setValidation] = useState({ valid: true, error: null });

  useEffect(() => {
    if (value) {
      const result = TenantService.validateSlug(value);
      setValidation(result);
    }
  }, [value]);

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => onChange(e.target.value.toLowerCase())}
        className={validation.valid ? '' : 'error'}
      />
      {!validation.valid && (
        <span className="error-message">{validation.error}</span>
      )}
      {validation.valid && value && (
        <span className="success-message">✓ Valid slug</span>
      )}
    </div>
  );
}
```

## Testing the Tenant API

### 1. Prerequisites

- Backend running at `http://localhost:8000`
- Frontend running at `http://localhost:5173`
- User logged in (valid JWT token)

### 2. Test Create Tenant

```javascript
// In browser console after login

import TenantService from './services/API/TenantService';

// Generate slug from name
const slug = TenantService.generateSlug("My New Company");
console.log("Generated slug:", slug); // "my-new-company"

// Validate before creating
const validation = TenantService.validateSlug(slug);
console.log("Slug valid:", validation.valid); // true

// Create tenant
const tenant = await TenantService.createTenant({
  name: "My New Company",
  slug: slug
});

console.log("Created tenant:", tenant);
```

### 3. Test Get Tenant by ID

```javascript
// Using tenant ID from creation
const tenant = await TenantService.getTenantById("850e8400-...");
console.log("Tenant by ID:", tenant);
```

### 4. Test Get Tenant by Slug

```javascript
const tenant = await TenantService.getTenantBySlug("my-new-company");
console.log("Tenant by slug:", tenant);
```

### 5. Test with curl

```bash
# 1. Login and get access token
ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "test@example.com",
    "password": "SecurePass123!"
  }' | jq -r '.access_token')

# 2. Create tenant
curl -X POST http://localhost:8000/api/v1/tenants \
  -H "Content-Type: application/json" \
  -H "Authorization: JWT $ACCESS_TOKEN" \
  -d '{
    "name": "Test Company",
    "slug": "test-company"
  }' | jq

# 3. Get tenant by ID (use ID from creation response)
TENANT_ID="850e8400-e29b-41d4-a716-446655440000"
curl -X GET "http://localhost:8000/api/v1/tenants/$TENANT_ID" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

# 4. Get tenant by slug
curl -X GET "http://localhost:8000/api/v1/tenants/slug/test-company" \
  -H "Authorization: JWT $ACCESS_TOKEN" | jq

# 5. Test slug conflict (should fail with 409)
curl -X POST http://localhost:8000/api/v1/tenants \
  -H "Content-Type: application/json" \
  -H "Authorization: JWT $ACCESS_TOKEN" \
  -d '{
    "name": "Another Company",
    "slug": "test-company"
  }' | jq
# Expected: 409 Conflict
```

### 6. Test Slug Generation

```javascript
// Test various slug generation scenarios

const tests = [
  { input: "TechStart Inc", expected: "techstart-inc" },
  { input: "My Company!", expected: "my-company" },
  { input: "  Spaces  Everywhere  ", expected: "spaces-everywhere" },
  { input: "Multiple---Hyphens", expected: "multiple-hyphens" },
  { input: "123 Numbers", expected: "123-numbers" },
];

tests.forEach(({ input, expected }) => {
  const result = TenantService.generateSlug(input);
  console.log(`Input: "${input}"`);
  console.log(`Expected: "${expected}"`);
  console.log(`Got: "${result}"`);
  console.log(`✓ Pass: ${result === expected}\n`);
});
```

## Integration with AuthManager

The tenant created during registration is stored in AuthManager:

```javascript
import { useAuth } from './services/Services';
import TenantService from './services/API/TenantService';

const { authManager } = useAuth();

// Get stored tenant from AuthManager
const storedTenant = authManager.getTenant();
console.log("Stored tenant:", storedTenant);
// { id: "...", name: "...", slug: "..." }

// Fetch fresh tenant data from API
const freshTenant = await TenantService.getTenantById(storedTenant.id);
console.log("Fresh tenant:", freshTenant);

// Compare
if (storedTenant.name !== freshTenant.name) {
  console.warn("Tenant data has changed");
}
```

## Multi-Tenant Context

Tenants provide isolation for multi-tenant applications:

```javascript
// Get current user's tenant
import MeService from './services/API/MeService';
import TenantService from './services/API/TenantService';

const profile = await MeService.getMe();
const tenantId = profile.tenantId;

// Get full tenant details
const tenant = await TenantService.getTenantById(tenantId);
console.log("Current tenant:", tenant.name);

// Use tenant context for operations
console.log("Working in tenant:", tenant.slug);
```

## Use Cases

### 1. Organization Switcher
Allow users to switch between tenants (if they belong to multiple).

### 2. Tenant Profile Display
Show tenant information in dashboard header or settings.

### 3. Tenant Creation Wizard
Guide users through creating a new tenant/organization.

### 4. Tenant Settings Page
Display and edit tenant information.

### 5. Multi-Tenant Routing
Use tenant slug in URLs (e.g., `/t/acme-corp/dashboard`).

## Troubleshooting

### "Tenant slug already exists" error

**Possible causes:**
1. Slug is already taken by another tenant
2. User trying to create duplicate tenant

**Solution:**
- Try a different slug
- Add numbers or suffix to make it unique
- Use the slug validation before submitting

### "Tenant not found" error

**Possible causes:**
1. Tenant ID is incorrect
2. Tenant was deleted
3. User doesn't have access to tenant

**Solution:**
- Verify tenant ID is correct UUID format
- Check if tenant still exists
- Verify user has proper access rights

### Slug validation fails unexpectedly

**Possible causes:**
1. Special characters in slug
2. Uppercase letters
3. Leading/trailing spaces or hyphens

**Solution:**
- Use `generateSlug()` to auto-generate valid slug
- Use `validateSlug()` before submitting
- Convert to lowercase and trim whitespace

## Related Files

### Created Files
```
src/services/API/TenantService.js
docs/TENANT_API.md
```

### Backend Reference Files
```
cloud/maplepress-backend/docs/API.md (lines 416-558)
cloud/maplepress-backend/internal/usecase/tenant/
cloud/maplepress-backend/internal/repository/tenant/
```

## Related Documentation

- [REGISTRATION_API.md](./REGISTRATION_API.md) - Initial tenant creation during registration
- [ME_API.md](./ME_API.md) - User profile includes tenant ID
- [FRONTEND_ARCHITECTURE.md](./FRONTEND_ARCHITECTURE.md) - Architecture overview
- [README.md](./README.md) - Documentation index
- [Backend API Documentation](../../../cloud/maplepress-backend/docs/API.md) - Complete API reference

## Summary

The Tenant API implementation provides:

1. **Tenant Creation**: Create new organizations with validation
2. **Tenant Retrieval**: Get tenant by ID or slug
3. **Slug Generation**: Auto-generate URL-friendly slugs
4. **Validation Helpers**: Client-side validation before API calls
5. **Error Handling**: Clear error messages and graceful failures
6. **Multi-Tenant Support**: Foundation for multi-tenant architecture

This is essential for managing organizations in a multi-tenant SaaS application.

---

**Last Updated**: October 30, 2024
**Frontend Version**: 0.0.0
**Documentation Version**: 1.0.0
