Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
792
web/maplepress-frontend/docs/API/TENANT_API.md
Normal file
792
web/maplepress-frontend/docs/API/TENANT_API.md
Normal file
|
|
@ -0,0 +1,792 @@
|
|||
# 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue