383 lines
10 KiB
Markdown
383 lines
10 KiB
Markdown
|
|
# 📋 Module Update Checklist untuk Multi-Client Support
|
||
|
|
|
||
|
|
## Status Saat Ini
|
||
|
|
|
||
|
|
❌ **Module BELUM disesuaikan** dengan entity baru
|
||
|
|
✅ **Entity sudah diupdate** (Clients, Users, UserClientAccess)
|
||
|
|
✅ **Middleware baru sudah dibuat** (ClientMiddlewareV2)
|
||
|
|
✅ **Utilities sudah dibuat** (client_hierarchy, client_utils_v2)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Module yang PERLU Diupdate
|
||
|
|
|
||
|
|
### Priority 1: CRITICAL (Harus diupdate dulu)
|
||
|
|
|
||
|
|
#### ✅ 1. Module: `clients` (PARTIALLY DONE)
|
||
|
|
**Status:** Request & Response V2 sudah dibuat
|
||
|
|
**TODO:**
|
||
|
|
- [ ] Update `clients.repository.go` - Add hierarchy queries
|
||
|
|
- [ ] Update `clients.service.go` - Add hierarchy logic
|
||
|
|
- [ ] Update `clients.controller.go` - Add new endpoints
|
||
|
|
- [ ] Update `clients.mapper.go` - Support new fields
|
||
|
|
|
||
|
|
**Files Updated:**
|
||
|
|
- ✅ `request/clients.request.v2.go` - NEW
|
||
|
|
- ✅ `response/clients.response.v2.go` - NEW
|
||
|
|
|
||
|
|
**New Endpoints Needed:**
|
||
|
|
```go
|
||
|
|
GET /api/v2/clients/:id/hierarchy // Get client tree
|
||
|
|
GET /api/v2/clients/:id/sub-clients // Get direct children
|
||
|
|
POST /api/v2/clients/:id/sub-clients // Create sub-client
|
||
|
|
PUT /api/v2/clients/:id/move // Move to different parent
|
||
|
|
GET /api/v2/clients/:id/stats // Get client statistics
|
||
|
|
POST /api/v2/clients/bulk-sub-clients // Bulk create sub-clients
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### ⚠️ 2. Module: `users`
|
||
|
|
**Impact:** HIGH - Users sekarang bisa multi-client access
|
||
|
|
**TODO:**
|
||
|
|
- [ ] Add endpoint untuk get accessible clients
|
||
|
|
- [ ] Add endpoint untuk switch current client
|
||
|
|
- [ ] Update user creation untuk set is_super_admin
|
||
|
|
- [ ] Update user list untuk show client access info
|
||
|
|
|
||
|
|
**New Endpoints Needed:**
|
||
|
|
```go
|
||
|
|
GET /api/v2/users/me/accessible-clients // Get my accessible clients
|
||
|
|
POST /api/v2/users/me/switch-client // Switch active client
|
||
|
|
GET /api/v2/users/:id/client-access // Get user's client access
|
||
|
|
POST /api/v2/users/:id/grant-client-access // Grant multi-client access
|
||
|
|
DELETE /api/v2/users/:id/revoke-client-access // Revoke client access
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### ⚠️ 3. Module: `user_client_access` (NEW MODULE)
|
||
|
|
**Status:** NEED TO CREATE
|
||
|
|
**Purpose:** Manage many-to-many User ↔ Client relationships
|
||
|
|
|
||
|
|
**Structure:**
|
||
|
|
```
|
||
|
|
app/module/user_client_access/
|
||
|
|
├── user_client_access.module.go
|
||
|
|
├── controller/
|
||
|
|
│ └── user_client_access.controller.go
|
||
|
|
├── service/
|
||
|
|
│ └── user_client_access.service.go
|
||
|
|
├── repository/
|
||
|
|
│ └── user_client_access.repository.go
|
||
|
|
├── request/
|
||
|
|
│ └── user_client_access.request.go
|
||
|
|
└── response/
|
||
|
|
└── user_client_access.response.go
|
||
|
|
```
|
||
|
|
|
||
|
|
**Endpoints:**
|
||
|
|
```go
|
||
|
|
GET /api/v2/user-client-access // List all access grants
|
||
|
|
POST /api/v2/user-client-access // Grant access
|
||
|
|
DELETE /api/v2/user-client-access/:id // Revoke access
|
||
|
|
GET /api/v2/user-client-access/user/:id // Get by user
|
||
|
|
GET /api/v2/user-client-access/client/:id // Get by client
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Priority 2: MEDIUM (Update untuk consistency)
|
||
|
|
|
||
|
|
#### 🔸 4. Module: `articles`
|
||
|
|
**Impact:** MEDIUM - Perlu support multi-client filtering
|
||
|
|
**TODO:**
|
||
|
|
- [ ] Update repository `GetAll()` - Use `AddMultiClientFilter()`
|
||
|
|
- [ ] Update repository `FindOne()` - Validate client access
|
||
|
|
- [ ] Update service untuk validate user has access to target client
|
||
|
|
- [ ] Update controller untuk support `?client_id=xxx` filter
|
||
|
|
|
||
|
|
**Changes Needed:**
|
||
|
|
```go
|
||
|
|
// OLD
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// NEW
|
||
|
|
func (r *Repository) GetAll(c *fiber.Ctx, req request.QueryRequest) {
|
||
|
|
query := r.DB.Model(&entity.Articles{})
|
||
|
|
|
||
|
|
// Auto-filter by accessible clients
|
||
|
|
query = middlewareUtils.AddMultiClientFilter(query, c)
|
||
|
|
|
||
|
|
// Optional: specific client filter
|
||
|
|
if req.ClientId != nil {
|
||
|
|
// Validate access first
|
||
|
|
query = query.Where("client_id = ?", req.ClientId)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### 🔸 5. Module: `user_roles`
|
||
|
|
**Impact:** MEDIUM - Roles mungkin specific per client
|
||
|
|
**TODO:**
|
||
|
|
- [ ] Update untuk support multi-client role access
|
||
|
|
- [ ] Add logic untuk inherit roles dari parent client (optional)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### 🔸 6. Module: `user_levels`
|
||
|
|
**Impact:** MEDIUM - Levels mungkin berbeda per client
|
||
|
|
**TODO:**
|
||
|
|
- [ ] Update untuk support multi-client level definitions
|
||
|
|
- [ ] Add hierarchy logic untuk levels across clients
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Priority 3: LOW (Optional updates)
|
||
|
|
|
||
|
|
Modules berikut juga punya `ClientId` field, tapi update bisa dilakukan bertahap:
|
||
|
|
|
||
|
|
- `schedules` - Update filtering
|
||
|
|
- `feedbacks` - Update filtering
|
||
|
|
- `subscriptions` - Update filtering
|
||
|
|
- `magazines` - Update filtering
|
||
|
|
- `advertisements` - Update filtering
|
||
|
|
- `article_categories` - Update filtering
|
||
|
|
- `bookmarks` - Update filtering
|
||
|
|
|
||
|
|
**Pattern sama untuk semua:**
|
||
|
|
```go
|
||
|
|
// Replace single-client filter
|
||
|
|
query = query.Where("client_id = ?", clientId)
|
||
|
|
|
||
|
|
// With multi-client filter
|
||
|
|
query = middlewareUtils.AddMultiClientFilter(query, c)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🚀 Quick Start Guide
|
||
|
|
|
||
|
|
### Step 1: Update Module Clients (Paling Penting)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 1. Buat file baru
|
||
|
|
touch app/module/clients/repository/clients.repository.v2.go
|
||
|
|
touch app/module/clients/service/clients.service.v2.go
|
||
|
|
touch app/module/clients/controller/clients.controller.v2.go
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 2: Create UserClientAccess Module
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 2. Buat module baru
|
||
|
|
mkdir -p app/module/user_client_access/{controller,service,repository,request,response}
|
||
|
|
|
||
|
|
# Generate files (atau copy template)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 3: Update Existing Modules Bertahap
|
||
|
|
|
||
|
|
Update per module, testing setiap perubahan:
|
||
|
|
1. ✅ Update `articles` (most used)
|
||
|
|
2. ✅ Update `users`
|
||
|
|
3. ✅ Update other modules one by one
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Template untuk Update Repository
|
||
|
|
|
||
|
|
```go
|
||
|
|
package repository
|
||
|
|
|
||
|
|
import (
|
||
|
|
"netidhub-saas-be/app/database/entity"
|
||
|
|
middlewareUtils "netidhub-saas-be/utils/middleware"
|
||
|
|
"github.com/gofiber/fiber/v2"
|
||
|
|
"gorm.io/gorm"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Repository struct {
|
||
|
|
DB *gorm.DB
|
||
|
|
}
|
||
|
|
|
||
|
|
// OLD VERSION (single-client)
|
||
|
|
func (r *Repository) GetAllOld(clientId *uuid.UUID, req request.QueryRequest) ([]entity.Model, error) {
|
||
|
|
query := r.DB.Model(&entity.Model{})
|
||
|
|
|
||
|
|
if clientId != nil {
|
||
|
|
query = query.Where("client_id = ?", clientId)
|
||
|
|
}
|
||
|
|
|
||
|
|
var results []entity.Model
|
||
|
|
query.Find(&results)
|
||
|
|
return results, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// NEW VERSION (multi-client)
|
||
|
|
func (r *Repository) GetAll(c *fiber.Ctx, req request.QueryRequest) ([]entity.Model, error) {
|
||
|
|
query := r.DB.Model(&entity.Model{})
|
||
|
|
|
||
|
|
// ✨ One line - auto multi-client filtering!
|
||
|
|
query = middlewareUtils.AddMultiClientFilter(query, c)
|
||
|
|
|
||
|
|
// Rest of your existing logic
|
||
|
|
var results []entity.Model
|
||
|
|
query.Find(&results)
|
||
|
|
return results, nil
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Template untuk Update Service
|
||
|
|
|
||
|
|
```go
|
||
|
|
package service
|
||
|
|
|
||
|
|
import (
|
||
|
|
customMiddleware "netidhub-saas-be/app/middleware"
|
||
|
|
clientUtils "netidhub-saas-be/utils/client"
|
||
|
|
"github.com/gofiber/fiber/v2"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Service struct {
|
||
|
|
repo Repository
|
||
|
|
}
|
||
|
|
|
||
|
|
// Before creating/updating, validate client access
|
||
|
|
func (s *Service) Create(c *fiber.Ctx, req request.CreateRequest) error {
|
||
|
|
userId := c.Locals(customMiddleware.UserIDContextKey).(uint)
|
||
|
|
isSuperAdmin := customMiddleware.IsSuperAdmin(c)
|
||
|
|
|
||
|
|
// If user specified a client, verify access
|
||
|
|
if req.ClientId != nil {
|
||
|
|
hasAccess, err := clientUtils.HasAccessToClient(
|
||
|
|
s.repo.DB,
|
||
|
|
userId,
|
||
|
|
*req.ClientId,
|
||
|
|
isSuperAdmin,
|
||
|
|
)
|
||
|
|
|
||
|
|
if err != nil || !hasAccess {
|
||
|
|
return fiber.NewError(403, "No access to this client")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Continue with creation
|
||
|
|
return s.repo.Create(req)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📝 Template untuk Update Controller
|
||
|
|
|
||
|
|
```go
|
||
|
|
package controller
|
||
|
|
|
||
|
|
import (
|
||
|
|
customMiddleware "netidhub-saas-be/app/middleware"
|
||
|
|
"github.com/gofiber/fiber/v2"
|
||
|
|
)
|
||
|
|
|
||
|
|
type Controller struct {
|
||
|
|
service Service
|
||
|
|
}
|
||
|
|
|
||
|
|
// List with multi-client support
|
||
|
|
func (ctrl *Controller) GetAll(c *fiber.Ctx) error {
|
||
|
|
var req request.QueryRequest
|
||
|
|
c.QueryParser(&req)
|
||
|
|
|
||
|
|
// Service/Repository will handle multi-client filtering
|
||
|
|
results, err := ctrl.service.GetAll(c, req)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
return c.Status(500).JSON(fiber.Map{
|
||
|
|
"success": false,
|
||
|
|
"message": "Failed to retrieve data",
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Show current context info (helpful for debugging)
|
||
|
|
isSuperAdmin := customMiddleware.IsSuperAdmin(c)
|
||
|
|
accessibleClients := customMiddleware.GetAccessibleClientIDs(c)
|
||
|
|
|
||
|
|
return c.JSON(fiber.Map{
|
||
|
|
"success": true,
|
||
|
|
"data": results,
|
||
|
|
"meta": fiber.Map{
|
||
|
|
"is_super_admin": isSuperAdmin,
|
||
|
|
"accessible_clients_count": len(accessibleClients),
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Testing Checklist
|
||
|
|
|
||
|
|
Untuk setiap module yang diupdate:
|
||
|
|
|
||
|
|
- [ ] **Super Admin** bisa lihat semua data (no client filter)
|
||
|
|
- [ ] **Multi-client user** hanya lihat data dari accessible clients
|
||
|
|
- [ ] **Single-client user** hanya lihat data dari 1 client (backward compat)
|
||
|
|
- [ ] **Create** data dengan client_id validation
|
||
|
|
- [ ] **Update** data dengan access check
|
||
|
|
- [ ] **Delete** data dengan access check
|
||
|
|
- [ ] **Filter by client_id** works correctly
|
||
|
|
- [ ] **Performance** tidak menurun significant
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📊 Progress Tracking
|
||
|
|
|
||
|
|
| Module | Priority | Status | Updated By | Date |
|
||
|
|
|--------|----------|--------|------------|------|
|
||
|
|
| clients | P1 | 🟡 In Progress | - | - |
|
||
|
|
| users | P1 | ⚪ Not Started | - | - |
|
||
|
|
| user_client_access | P1 | ⚪ Not Started | - | - |
|
||
|
|
| articles | P2 | ⚪ Not Started | - | - |
|
||
|
|
| user_roles | P2 | ⚪ Not Started | - | - |
|
||
|
|
| user_levels | P2 | ⚪ Not Started | - | - |
|
||
|
|
| schedules | P3 | ⚪ Not Started | - | - |
|
||
|
|
| feedbacks | P3 | ⚪ Not Started | - | - |
|
||
|
|
| subscriptions | P3 | ⚪ Not Started | - | - |
|
||
|
|
|
||
|
|
**Legend:**
|
||
|
|
- ⚪ Not Started
|
||
|
|
- 🟡 In Progress
|
||
|
|
- 🟢 Completed
|
||
|
|
- 🔴 Blocked
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🆘 Need Help?
|
||
|
|
|
||
|
|
Jika stuck atau ada pertanyaan:
|
||
|
|
1. Lihat `docs/examples/articles_controller_example.go` untuk contoh lengkap
|
||
|
|
2. Lihat `docs/MULTI_CLIENT_ACCESS_GUIDE.md` untuk penjelasan konsep
|
||
|
|
3. Check `docs/IMPLEMENTATION_SUMMARY.md` untuk overview
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🎯 Estimated Timeline
|
||
|
|
|
||
|
|
- **Week 1:** Update module `clients` + Create `user_client_access` module
|
||
|
|
- **Week 2:** Update module `users` + `articles`
|
||
|
|
- **Week 3:** Update remaining P2 modules
|
||
|
|
- **Week 4:** Update P3 modules + Testing
|
||
|
|
|
||
|
|
**Total: ~1 month** untuk full implementation semua modules.
|
||
|
|
|
||
|
|
Bisa lebih cepat jika dikerjakan parallel atau prioritas hanya module penting saja.
|