feat(admin): Implement comprehensive CLI admin management functionality
- Add new `internal/cli/admin.go` package for admin management - Implement interactive admin account creation with secure password input - Add CLI command for creating admin accounts with flexible input options - Implement validation for admin account creation details - Support both interactive and flag-based admin account creation - Integrate with existing user and authentication services - Update go.mod and go.sum with new dependencies and version upgrades Enhances system administration capabilities by providing a flexible CLI tool for creating admin accounts with robust security and usability features.
This commit is contained in:
parent
c265e8f866
commit
df39550eb1
3
go.mod
3
go.mod
@ -47,7 +47,8 @@ require (
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@ -109,7 +109,11 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
|
||||
212
internal/cli/admin.go
Normal file
212
internal/cli/admin.go
Normal file
@ -0,0 +1,212 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"attune-heart-therapy/internal/config"
|
||||
"attune-heart-therapy/internal/database"
|
||||
"attune-heart-therapy/internal/services"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var adminCmd = &cobra.Command{
|
||||
Use: "admin",
|
||||
Short: "Admin management commands",
|
||||
Long: "Commands for managing admin accounts and operations",
|
||||
}
|
||||
|
||||
var createAdminCmd = &cobra.Command{
|
||||
Use: "create-admin",
|
||||
Short: "Create a new admin account",
|
||||
Long: "Create a new admin account with email and password parameters",
|
||||
Run: runCreateAdmin,
|
||||
}
|
||||
|
||||
// Command flags
|
||||
var (
|
||||
adminEmail string
|
||||
adminPassword string
|
||||
adminFirstName string
|
||||
adminLastName string
|
||||
interactive bool
|
||||
)
|
||||
|
||||
func runCreateAdmin(cmd *cobra.Command, args []string) {
|
||||
// Load environment variables
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Println("No .env file found, using system environment variables")
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load configuration: %v", err)
|
||||
}
|
||||
|
||||
// Initialize database connection
|
||||
db, err := database.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Get repositories
|
||||
repos := db.GetRepositories()
|
||||
|
||||
// Initialize services
|
||||
jwtService := services.NewJWTService(cfg.JWT.Secret, cfg.JWT.Expiration)
|
||||
notificationService := services.NewNotificationService(repos.Notification, cfg)
|
||||
userService := services.NewUserService(repos.User, jwtService, notificationService)
|
||||
|
||||
// Get admin details
|
||||
if interactive || adminEmail == "" || adminPassword == "" {
|
||||
if err := getAdminDetailsInteractively(); err != nil {
|
||||
log.Fatalf("Failed to get admin details: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if err := validateAdminInput(); err != nil {
|
||||
log.Fatalf("Validation failed: %v", err)
|
||||
}
|
||||
|
||||
// Create admin account
|
||||
if err := createAdminAccount(userService); err != nil {
|
||||
log.Fatalf("Failed to create admin account: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Admin account created successfully for: %s\n", adminEmail)
|
||||
}
|
||||
|
||||
func getAdminDetailsInteractively() error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
// Get email
|
||||
if adminEmail == "" {
|
||||
fmt.Print("Enter admin email: ")
|
||||
email, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read email: %w", err)
|
||||
}
|
||||
adminEmail = strings.TrimSpace(email)
|
||||
}
|
||||
|
||||
// Get first name
|
||||
if adminFirstName == "" {
|
||||
fmt.Print("Enter admin first name: ")
|
||||
firstName, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read first name: %w", err)
|
||||
}
|
||||
adminFirstName = strings.TrimSpace(firstName)
|
||||
}
|
||||
|
||||
// Get last name
|
||||
if adminLastName == "" {
|
||||
fmt.Print("Enter admin last name: ")
|
||||
lastName, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read last name: %w", err)
|
||||
}
|
||||
adminLastName = strings.TrimSpace(lastName)
|
||||
}
|
||||
|
||||
// Get password securely
|
||||
if adminPassword == "" {
|
||||
fmt.Print("Enter admin password: ")
|
||||
passwordBytes, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read password: %w", err)
|
||||
}
|
||||
adminPassword = string(passwordBytes)
|
||||
fmt.Println() // Add newline after password input
|
||||
|
||||
// Confirm password
|
||||
fmt.Print("Confirm admin password: ")
|
||||
confirmPasswordBytes, err := terminal.ReadPassword(int(syscall.Stdin))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read password confirmation: %w", err)
|
||||
}
|
||||
confirmPassword := string(confirmPasswordBytes)
|
||||
fmt.Println() // Add newline after password confirmation
|
||||
|
||||
if adminPassword != confirmPassword {
|
||||
return fmt.Errorf("passwords do not match")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAdminInput() error {
|
||||
if adminEmail == "" {
|
||||
return fmt.Errorf("email is required")
|
||||
}
|
||||
|
||||
if adminFirstName == "" {
|
||||
return fmt.Errorf("first name is required")
|
||||
}
|
||||
|
||||
if adminLastName == "" {
|
||||
return fmt.Errorf("last name is required")
|
||||
}
|
||||
|
||||
if adminPassword == "" {
|
||||
return fmt.Errorf("password is required")
|
||||
}
|
||||
|
||||
if len(adminPassword) < 8 {
|
||||
return fmt.Errorf("password must be at least 8 characters long")
|
||||
}
|
||||
|
||||
// Basic email validation
|
||||
if !strings.Contains(adminEmail, "@") || !strings.Contains(adminEmail, ".") {
|
||||
return fmt.Errorf("invalid email format")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createAdminAccount(userService services.UserService) error {
|
||||
// Create admin request
|
||||
adminRequest := services.CreateAdminRequest{
|
||||
FirstName: adminFirstName,
|
||||
LastName: adminLastName,
|
||||
Email: adminEmail,
|
||||
Password: adminPassword,
|
||||
}
|
||||
|
||||
// Create admin account using the user service
|
||||
adminUser, err := userService.CreateAdmin(adminRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create admin account: %w", err)
|
||||
}
|
||||
|
||||
// Log success (adminUser contains the created user info)
|
||||
_ = adminUser
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add flags to create-admin command
|
||||
createAdminCmd.Flags().StringVarP(&adminEmail, "email", "e", "", "Admin email address")
|
||||
createAdminCmd.Flags().StringVarP(&adminPassword, "password", "p", "", "Admin password")
|
||||
createAdminCmd.Flags().StringVarP(&adminFirstName, "first-name", "f", "", "Admin first name")
|
||||
createAdminCmd.Flags().StringVarP(&adminLastName, "last-name", "l", "", "Admin last name")
|
||||
createAdminCmd.Flags().BoolVarP(&interactive, "interactive", "i", false, "Interactive mode for entering admin details")
|
||||
|
||||
// Add subcommands to admin command
|
||||
adminCmd.AddCommand(createAdminCmd)
|
||||
|
||||
// Add admin command to root
|
||||
rootCmd.AddCommand(adminCmd)
|
||||
}
|
||||
@ -14,6 +14,7 @@ type UserService interface {
|
||||
Login(email, password string) (*models.User, string, error) // returns user and JWT token
|
||||
GetProfile(userID uint) (*models.User, error)
|
||||
UpdateProfile(userID uint, req UpdateProfileRequest) (*models.User, error)
|
||||
CreateAdmin(req CreateAdminRequest) (*models.User, error)
|
||||
}
|
||||
|
||||
// BookingService handles booking operations
|
||||
@ -146,3 +147,10 @@ type UpdateScheduleRequest struct {
|
||||
MaxBookings *int `json:"max_bookings"`
|
||||
IsAvailable *bool `json:"is_available"`
|
||||
}
|
||||
|
||||
type CreateAdminRequest struct {
|
||||
FirstName string `json:"first_name" validate:"required,min=2,max=100"`
|
||||
LastName string `json:"last_name" validate:"required,min=2,max=100"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
}
|
||||
|
||||
@ -180,3 +180,48 @@ func (s *userService) UpdateProfile(userID uint, req UpdateProfileRequest) (*mod
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// CreateAdmin creates a new admin account with validation and password hashing
|
||||
func (s *userService) CreateAdmin(req CreateAdminRequest) (*models.User, error) {
|
||||
// Validate the request
|
||||
if err := s.validator.Struct(req); err != nil {
|
||||
return nil, fmt.Errorf("validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Additional password validation
|
||||
if len(req.Password) < 8 {
|
||||
return nil, errors.New("password must be at least 8 characters long")
|
||||
}
|
||||
|
||||
// Normalize email
|
||||
req.Email = strings.ToLower(strings.TrimSpace(req.Email))
|
||||
|
||||
// Check if user already exists
|
||||
existingUser, err := s.userRepo.GetByEmail(req.Email)
|
||||
if err == nil && existingUser != nil {
|
||||
return nil, fmt.Errorf("user with email %s already exists", req.Email)
|
||||
}
|
||||
|
||||
// Create new admin user
|
||||
user := &models.User{
|
||||
FirstName: strings.TrimSpace(req.FirstName),
|
||||
LastName: strings.TrimSpace(req.LastName),
|
||||
Email: req.Email,
|
||||
IsAdmin: true, // Set admin flag
|
||||
}
|
||||
|
||||
// Hash the password
|
||||
if err := user.HashPassword(req.Password); err != nil {
|
||||
return nil, fmt.Errorf("failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
// Save user to database
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
return nil, fmt.Errorf("failed to create admin user: %w", err)
|
||||
}
|
||||
|
||||
// Clear password hash from response for security
|
||||
user.PasswordHash = ""
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user