# Multi-Client Access & Client Hierarchy Guide

## 📋 Overview

Sistem ini mendukung **hierarchical multi-tenancy** dengan fitur:
- ✅ Parent-Child Client relationships (unlimited depth)
- ✅ User dapat memiliki akses ke multiple clients
- ✅ Super Admin dengan akses ke semua clients
- ✅ Fine-grained access control (read, write, admin, owner)
- ✅ Automatic sub-client inheritance

---

## 🏗️ Arsitektur

### Client Types

1. **Standalone Client** - Client mandiri tanpa parent/child
2. **Parent Client** - Client yang memiliki sub-clients
3. **Sub Client** - Client yang berada di bawah parent client

### User Types

1. **Super Admin** - Platform administrator, akses ke SEMUA clients
2. **Multi-Client User** - User dengan akses ke beberapa clients (misal: manager regional)
3. **Single-Client User** - User regular dengan akses ke 1 client saja

---

## 🗄️ Database Schema

### Entity: Clients

```go
type Clients struct {
    ID             uuid.UUID  
    Name           string     
    Description    *string    
    ClientType     string     // 'parent_client', 'sub_client', 'standalone'
    ParentClientId *uuid.UUID // Reference to parent
    
    Settings       *string    // JSONB custom settings
    MaxUsers       *int       
    MaxStorage     *int64     
    
    CreatedById    *uint      
    IsActive       *bool      
}
```

### Entity: UserClientAccess (Many-to-Many)

```go
type UserClientAccess struct {
    ID                uint       
    UserId            uint       
    ClientId          uuid.UUID  
    
    AccessType        string     // 'read', 'write', 'admin', 'owner'
    CanManage         *bool      // Can manage client settings
    CanDelegate       *bool      // Can give access to other users
    IncludeSubClients *bool      // Auto-access to all sub-clients
    
    GrantedById       *uint      
    IsActive          *bool      
}
```

### Entity: Users (Updated)

```go
type Users struct {
    // ... existing fields
    
    IsSuperAdmin   *bool               // Platform super admin
    ClientId       *uuid.UUID          // Primary client
    ClientAccesses []UserClientAccess  // Multiple client access
}
```

---

## 🚀 Migration Steps

### Step 1: Run Database Migration

```bash
# Migration akan otomatis dijalankan saat aplikasi start
# Pastikan config migrate = true di config.toml

go run main.go
```

### Step 2: Migrate Existing Data

```sql
-- 1. Set semua existing clients sebagai 'standalone'
UPDATE clients 
SET client_type = 'standalone' 
WHERE client_type IS NULL;

-- 2. Create super admin user (contoh)
UPDATE users 
SET is_super_admin = true 
WHERE id = 1; -- Ganti dengan ID admin utama Anda

-- 3. Migrasi user existing ke UserClientAccess (optional)
-- Untuk user yang perlu akses ke multiple clients
INSERT INTO user_client_access (user_id, client_id, access_type, can_manage, is_active, created_at, updated_at)
SELECT 
    id as user_id,
    client_id,
    'admin' as access_type,
    true as can_manage,
    true as is_active,
    NOW(),
    NOW()
FROM users 
WHERE client_id IS NOT NULL 
AND is_super_admin = false;
```

### Step 3: Update Code

#### Option A: Gunakan Middleware V2 (Recommended)

Update di `config/webserver/webserver.config.go`:

```go
// Ganti middleware lama
// app.Use(middleware.ClientMiddleware(db.DB))

// Dengan middleware baru
app.Use(middleware.ClientMiddlewareV2(db.DB))
```

#### Option B: Bertahap (Backward Compatible)

Kedua middleware bisa berjalan bersamaan:
```go
// Keep old middleware for backward compatibility
app.Use(middleware.ClientMiddleware(db.DB))

// Routes baru bisa pakai V2
apiV2 := app.Group("/api/v2")
apiV2.Use(middleware.ClientMiddlewareV2(db.DB))
```

---

## 💻 Contoh Penggunaan

### 1. Setup Parent-Child Clients

```go
// Create Parent Client
parentClient := entity.Clients{
    ID:          uuid.New(),
    Name:        "Polda Metro Jaya",
    ClientType:  "parent_client",
    IsActive:    &trueVal,
}
db.Create(&parentClient)

// Create Sub Clients
subClient1 := entity.Clients{
    ID:             uuid.New(),
    Name:           "Polres Jakarta Pusat",
    ClientType:     "sub_client",
    ParentClientId: &parentClient.ID,
    IsActive:       &trueVal,
}
db.Create(&subClient1)

subClient2 := entity.Clients{
    ID:             uuid.New(),
    Name:           "Polres Jakarta Barat",
    ClientType:     "sub_client",
    ParentClientId: &parentClient.ID,
    IsActive:       &trueVal,
}
db.Create(&subClient2)
```

### 2. Grant Multi-Client Access

```go
import "netidhub-saas-be/app/database/entity"

// User bisa manage multiple clients
access1 := entity.UserClientAccess{
    UserId:            managerUser.ID,
    ClientId:          parentClient.ID,
    AccessType:        "admin",
    CanManage:         &trueVal,
    IncludeSubClients: &trueVal, // Auto-access semua sub-clients
    IsActive:          &trueVal,
}
db.Create(&access1)

access2 := entity.UserClientAccess{
    UserId:     managerUser.ID,
    ClientId:   anotherClient.ID,
    AccessType: "read",
    IsActive:   &trueVal,
}
db.Create(&access2)
```

### 3. Update Repository dengan Multi-Client Filter

**OLD CODE:**
```go
func (r *Repository) GetAll(clientId *uuid.UUID, req request.QueryRequest) {
    query := r.DB.Model(&entity.Articles{})
    
    if clientId != nil {
        query = query.Where("client_id = ?", clientId)
    }
    // ... rest of code
}
```

**NEW CODE:**
```go
import middlewareUtils "netidhub-saas-be/utils/middleware"

func (r *Repository) GetAll(c *fiber.Ctx, req request.QueryRequest) {
    query := r.DB.Model(&entity.Articles{})
    
    // Automatically filter by accessible clients
    query = middlewareUtils.AddMultiClientFilter(query, c)
    
    // ... rest of code
}
```

### 4. Check User Access di Controller

```go
import (
    customMiddleware "netidhub-saas-be/app/middleware"
    clientUtils "netidhub-saas-be/utils/client"
)

func (ctrl *Controller) GetArticle(c *fiber.Ctx) error {
    articleId := c.Params("id")
    
    // Get user info
    userId := c.Locals(customMiddleware.UserIDContextKey).(uint)
    isSuperAdmin := customMiddleware.IsSuperAdmin(c)
    
    // Get article
    var article entity.Articles
    db.First(&article, articleId)
    
    // Check access
    hasAccess, _ := clientUtils.HasAccessToClient(
        db, 
        userId, 
        *article.ClientId, 
        isSuperAdmin,
    )
    
    if !hasAccess {
        return c.Status(403).JSON(fiber.Map{
            "error": "Access denied",
        })
    }
    
    return c.JSON(article)
}
```

### 5. Get Client Hierarchy

```go
import clientUtils "netidhub-saas-be/utils/client"

func (ctrl *Controller) GetClientInfo(c *fiber.Ctx) error {
    clientId := c.Params("id")
    clientUUID, _ := uuid.Parse(clientId)
    
    // Get full hierarchy
    client, err := clientUtils.GetClientHierarchy(db, clientUUID)
    
    return c.JSON(fiber.Map{
        "client":      client,
        "parent":      client.ParentClient,
        "sub_clients": client.SubClients,
    })
}
```

---

## 🎯 Use Cases

### Use Case 1: Regional Manager

**Scenario:** Manager regional yang mengawasi 3 Polres

```go
// Grant access dengan IncludeSubClients
access := entity.UserClientAccess{
    UserId:            regionalManagerId,
    ClientId:          parentPolresId,
    AccessType:        "admin",
    IncludeSubClients: &trueVal, // Auto-akses semua polres di bawahnya
    CanManage:         &trueVal,
    IsActive:          &trueVal,
}
```

### Use Case 2: Super Admin Dashboard

```go
func (ctrl *AdminController) GetAllArticles(c *fiber.Ctx) error {
    // Super admin bisa lihat semua artikel dari semua clients
    if !customMiddleware.IsSuperAdmin(c) {
        return c.Status(403).JSON(fiber.Map{
            "error": "Super admin only",
        })
    }
    
    var articles []entity.Articles
    // No client filtering for super admin
    db.Find(&articles)
    
    return c.JSON(articles)
}
```

### Use Case 3: User Switching Between Clients

```go
// User bisa switch antara clients yang dia punya akses
func (ctrl *UserController) SwitchClient(c *fiber.Ctx) error {
    targetClientId := c.Params("client_id")
    clientUUID, _ := uuid.Parse(targetClientId)
    
    userId := c.Locals(customMiddleware.UserIDContextKey).(uint)
    
    // Verify user has access
    hasAccess, _ := clientUtils.HasAccessToClient(
        db, 
        userId, 
        clientUUID, 
        false,
    )
    
    if !hasAccess {
        return c.Status(403).JSON(fiber.Map{
            "error": "No access to this client",
        })
    }
    
    // Update user's primary client or set in session
    // Return success
}
```

---

## 🔒 Security Considerations

1. **Always validate client access** sebelum menampilkan/memodifikasi data
2. **Super admin flag** harus di-protect dengan ketat (database constraint)
3. **Audit trail** untuk perubahan UserClientAccess
4. **Cascade rules** untuk delete parent client
5. **Rate limiting** untuk multi-client queries

---

## 🐛 Troubleshooting

### Issue: User tidak bisa akses client

```go
// Debug: Check accessible clients
accessibleClients, _ := clientUtils.GetAccessibleClientIDs(db, userId, false)
fmt.Println("Accessible clients:", accessibleClients)
```

### Issue: Sub-clients tidak muncul

```go
// Pastikan IncludeSubClients = true
db.Model(&entity.UserClientAccess{}).
    Where("user_id = ?", userId).
    Update("include_sub_clients", true)
```

### Issue: Super admin tidak bisa akses

```go
// Verify super admin flag
var user entity.Users
db.First(&user, userId)
fmt.Println("Is Super Admin:", *user.IsSuperAdmin)
```

---

## 📊 Performance Tips

1. **Index** pada `parent_client_id` dan `user_id, client_id` (sudah ada di entity)
2. **Cache** accessible client IDs per user (Redis recommended)
3. **Batch queries** untuk sub-client retrieval
4. **Pagination** untuk multi-client data

---

## 🔄 Backward Compatibility

Kode lama tetap berfungsi:
- ✅ `middleware.GetClientID(c)` masih tersedia
- ✅ `ClientId` di Users tetap ada sebagai primary client
- ✅ Single-client filtering tetap work
- ✅ Header `X-Client-Key` masih didukung

Migration ke V2 bisa dilakukan bertahap per module.

---

## 📞 Support

Untuk pertanyaan atau issue, silakan hubungi tim development.
