# 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.