package iam import ( "fmt" "net/http" "strings" "github.com/atlasos/calypso/internal/common/database" "github.com/atlasos/calypso/internal/common/logger" "github.com/gin-gonic/gin" ) // Handler handles IAM-related requests type Handler struct { db *database.DB logger *logger.Logger } // NewHandler creates a new IAM handler func NewHandler(db *database.DB, log *logger.Logger) *Handler { return &Handler{ db: db, logger: log, } } // ListUsers lists all users func (h *Handler) ListUsers(c *gin.Context) { query := ` SELECT id, username, email, full_name, is_active, is_system, created_at, updated_at, last_login_at FROM users ORDER BY username ` rows, err := h.db.Query(query) if err != nil { h.logger.Error("Failed to list users", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list users"}) return } defer rows.Close() var users []map[string]interface{} for rows.Next() { var u struct { ID string Username string Email string FullName string IsActive bool IsSystem bool CreatedAt string UpdatedAt string LastLoginAt *string } if err := rows.Scan(&u.ID, &u.Username, &u.Email, &u.FullName, &u.IsActive, &u.IsSystem, &u.CreatedAt, &u.UpdatedAt, &u.LastLoginAt); err != nil { h.logger.Error("Failed to scan user", "error", err) continue } users = append(users, map[string]interface{}{ "id": u.ID, "username": u.Username, "email": u.Email, "full_name": u.FullName, "is_active": u.IsActive, "is_system": u.IsSystem, "created_at": u.CreatedAt, "updated_at": u.UpdatedAt, "last_login_at": u.LastLoginAt, }) } c.JSON(http.StatusOK, gin.H{"users": users}) } // GetUser retrieves a single user func (h *Handler) GetUser(c *gin.Context) { userID := c.Param("id") user, err := GetUserByID(h.db, userID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } roles, _ := GetUserRoles(h.db, userID) permissions, _ := GetUserPermissions(h.db, userID) c.JSON(http.StatusOK, gin.H{ "id": user.ID, "username": user.Username, "email": user.Email, "full_name": user.FullName, "is_active": user.IsActive, "is_system": user.IsSystem, "roles": roles, "permissions": permissions, "created_at": user.CreatedAt, "updated_at": user.UpdatedAt, }) } // CreateUser creates a new user func (h *Handler) CreateUser(c *gin.Context) { var req struct { Username string `json:"username" binding:"required"` Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` FullName string `json:"full_name"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } // TODO: Hash password with Argon2id passwordHash := req.Password // Placeholder query := ` INSERT INTO users (username, email, password_hash, full_name) VALUES ($1, $2, $3, $4) RETURNING id ` var userID string err := h.db.QueryRow(query, req.Username, req.Email, passwordHash, req.FullName).Scan(&userID) if err != nil { h.logger.Error("Failed to create user", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"}) return } h.logger.Info("User created", "user_id", userID, "username", req.Username) c.JSON(http.StatusCreated, gin.H{"id": userID, "username": req.Username}) } // UpdateUser updates an existing user func (h *Handler) UpdateUser(c *gin.Context) { userID := c.Param("id") var req struct { Email *string `json:"email"` FullName *string `json:"full_name"` IsActive *bool `json:"is_active"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) return } // Build update query dynamically updates := []string{"updated_at = NOW()"} args := []interface{}{} argPos := 1 if req.Email != nil { updates = append(updates, fmt.Sprintf("email = $%d", argPos)) args = append(args, *req.Email) argPos++ } if req.FullName != nil { updates = append(updates, fmt.Sprintf("full_name = $%d", argPos)) args = append(args, *req.FullName) argPos++ } if req.IsActive != nil { updates = append(updates, fmt.Sprintf("is_active = $%d", argPos)) args = append(args, *req.IsActive) argPos++ } if len(updates) == 1 { c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"}) return } args = append(args, userID) query := "UPDATE users SET " + strings.Join(updates, ", ") + fmt.Sprintf(" WHERE id = $%d", argPos) _, err := h.db.Exec(query, args...) if err != nil { h.logger.Error("Failed to update user", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update user"}) return } h.logger.Info("User updated", "user_id", userID) c.JSON(http.StatusOK, gin.H{"message": "user updated successfully"}) } // DeleteUser deletes a user func (h *Handler) DeleteUser(c *gin.Context) { userID := c.Param("id") // Check if user is system user var isSystem bool err := h.db.QueryRow("SELECT is_system FROM users WHERE id = $1", userID).Scan(&isSystem) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } if isSystem { c.JSON(http.StatusForbidden, gin.H{"error": "cannot delete system user"}) return } _, err = h.db.Exec("DELETE FROM users WHERE id = $1", userID) if err != nil { h.logger.Error("Failed to delete user", "error", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete user"}) return } h.logger.Info("User deleted", "user_id", userID) c.JSON(http.StatusOK, gin.H{"message": "user deleted successfully"}) }