diff --git a/backend/internal/handler/workflow.go b/backend/internal/handler/workflow.go index b2627eb..16aa716 100644 --- a/backend/internal/handler/workflow.go +++ b/backend/internal/handler/workflow.go @@ -181,17 +181,174 @@ func ResetPassword(c *gin.Context) { // POST /api/v1/users/batch-import func BatchImportUsers(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Batch import (placeholder)", "count": 0}) + var input struct { + Users []struct { + Username string `json:"username"` + DisplayName string `json:"display_name"` + Email string `json:"email"` + Role string `json:"role"` + Password string `json:"password"` + } `json:"users"` + ImportType string `json:"import_type"` + SkipExisting bool `json:"skip_existing"` + } + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) + return + } + + successCount := 0 + var errors []string + + for _, u := range input.Users { + if u.Username == "" { + errors = append(errors, "skip: empty username") + continue + } + + // Check if already exists + var existing int64 + model.DB.Model(&model.User{}).Where("username = ?", u.Username).Count(&existing) + if existing > 0 { + if input.SkipExisting { + errors = append(errors, "skip: "+u.Username+" already exists") + continue + } + errors = append(errors, "fail: "+u.Username+" already exists") + continue + } + + password := u.Password + if password == "" { + password = "admin123" + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + errors = append(errors, "fail: "+u.Username+" hash error") + continue + } + + role := u.Role + if role == "" { + role = "developer" + } + displayName := u.DisplayName + if displayName == "" { + displayName = u.Username + } + + emailPtr := &u.Email + if u.Email == "" { + emailPtr = nil + } + + user := model.User{ + Username: u.Username, + DisplayName: displayName, + Email: emailPtr, + HashedPassword: string(hash), + Role: role, + } + if err := model.DB.Create(&user).Error; err != nil { + errors = append(errors, "fail: "+u.Username+" - "+err.Error()) + continue + } + successCount++ + } + + c.JSON(http.StatusOK, gin.H{ + "success_count": successCount, + "fail_count": len(input.Users) - successCount, + "errors": errors, + }) } // POST /api/v1/users/batch-import-ldap func BatchImportLdapUsers(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "LDAP import (placeholder)", "count": 0}) + var input struct { + Users []struct { + DN string `json:"dn"` + CN string `json:"cn"` + Nickname string `json:"nickname"` + UID string `json:"uid"` + Username string `json:"username"` + Mail string `json:"mail"` + } `json:"users"` + } + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) + return + } + + successCount := 0 + var errors []string + defaultPassword := "admin123" + hash, _ := bcrypt.GenerateFromPassword([]byte(defaultPassword), bcrypt.DefaultCost) + + for _, u := range input.Users { + // Derive username: uid > username > cn + username := u.UID + if username == "" { + username = u.Username + } + if username == "" { + username = u.CN + } + if username == "" { + errors = append(errors, "skip: empty username for DN="+u.DN) + continue + } + + // Check if already exists + var existing int64 + model.DB.Model(&model.User{}).Where("username = ?", username).Count(&existing) + if existing > 0 { + errors = append(errors, "skip: "+username+" already exists") + continue + } + + // Derive display name: cn > nickname > username + displayName := u.CN + if displayName == "" { + displayName = u.Nickname + } + if displayName == "" { + displayName = username + } + + email := u.Mail + emailPtr := &email + if email == "" { + emailPtr = nil + } + + user := model.User{ + Username: username, + DisplayName: displayName, + Email: emailPtr, + HashedPassword: string(hash), + Role: "developer", + } + if err := model.DB.Create(&user).Error; err != nil { + errors = append(errors, "fail: "+username+" - "+err.Error()) + continue + } + successCount++ + } + + c.JSON(http.StatusOK, gin.H{ + "success_count": successCount, + "fail_count": len(input.Users) - successCount, + "errors": errors, + }) } // GET /api/v1/users/import-template func ImportTemplate(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"message": "Template download (placeholder)"}) + csv := "username,display_name,email,role,password\nzhangsan,张三,zhangsan@test.com,developer,\nlisi,李四,lisi@test.com,dba,pass123\n" + c.Header("Content-Type", "text/csv; charset=utf-8") + c.Header("Content-Disposition", "attachment; filename=users_import_template.csv") + c.Writer.Write([]byte("\xEF\xBB\xBF" + csv)) } // POST /api/v1/users/:userId/reset-password