fix(users): allow superadmin to list all users without tenant restriction
- Modified ListUsers handler to check for admin/superadmin role - Superadmins can now list all users across tenants - Added ListUsers method to AdminService - Added Status field to dto.User Fixes 403 error when superadmin tries to access /api/v1/users
This commit is contained in:
parent
546e253a5f
commit
73967ca52b
3 changed files with 85 additions and 3 deletions
|
|
@ -279,15 +279,58 @@ func (h *CoreHandlers) CreateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
// @Router /api/v1/users [get]
|
// @Router /api/v1/users [get]
|
||||||
func (h *CoreHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
func (h *CoreHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
// Check if user is admin/superadmin (they can list all users)
|
||||||
|
roles := middleware.ExtractRoles(ctx.Value(middleware.ContextRoles))
|
||||||
|
isAdmin := false
|
||||||
|
for _, role := range roles {
|
||||||
|
if role == "ADMIN" || role == "SUPERADMIN" || role == "admin" || role == "superadmin" {
|
||||||
|
isAdmin = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||||
|
if limit < 1 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
if limit > 100 {
|
||||||
|
limit = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAdmin {
|
||||||
|
// Admin view: List all users using AdminService
|
||||||
|
users, total, err := h.adminService.ListUsers(ctx, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"data": users,
|
||||||
|
"pagination": map[string]interface{}{
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"total": total,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-admin: require tenant ID
|
||||||
tenantID, ok := ctx.Value(middleware.ContextTenantID).(string)
|
tenantID, ok := ctx.Value(middleware.ContextTenantID).(string)
|
||||||
if !ok || tenantID == "" {
|
if !ok || tenantID == "" {
|
||||||
http.Error(w, "Tenant ID not found in context", http.StatusForbidden)
|
http.Error(w, "Tenant ID not found in context", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
||||||
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
||||||
|
|
||||||
users, err := h.listUsersUC.Execute(ctx, tenantID, page, limit)
|
users, err := h.listUsersUC.Execute(ctx, tenantID, page, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ type User struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
|
Status string `json:"status"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
CompanyID *string `json:"companyId,omitempty"`
|
CompanyID *string `json:"companyId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,44 @@ func (s *AdminService) ListCompanies(ctx context.Context, verified *bool, page,
|
||||||
return companies, total, nil
|
return companies, total, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListUsers returns all users with pagination (for admin view)
|
||||||
|
func (s *AdminService) ListUsers(ctx context.Context, page, limit int) ([]dto.User, int, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
|
// Count Total
|
||||||
|
var total int
|
||||||
|
if err := s.DB.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&total); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch Data
|
||||||
|
query := `
|
||||||
|
SELECT id, COALESCE(name, full_name, identifier, ''), email, role, COALESCE(status, 'active'), created_at
|
||||||
|
FROM users
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
LIMIT $1 OFFSET $2
|
||||||
|
`
|
||||||
|
|
||||||
|
rows, err := s.DB.QueryContext(ctx, query, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
users := []dto.User{}
|
||||||
|
for rows.Next() {
|
||||||
|
var u dto.User
|
||||||
|
var roleStr string
|
||||||
|
if err := rows.Scan(&u.ID, &u.Name, &u.Email, &roleStr, &u.Status, &u.CreatedAt); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
u.Role = roleStr
|
||||||
|
users = append(users, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id string, active *bool, verified *bool) (*models.Company, error) {
|
func (s *AdminService) UpdateCompanyStatus(ctx context.Context, id string, active *bool, verified *bool) (*models.Company, error) {
|
||||||
company, err := s.getCompanyByID(ctx, id)
|
company, err := s.getCompanyByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue