blob: 552a4dda99ba93ef270912ff7bc7de76a24ddab5 [file] [log] [blame]
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
"golang.org/x/term"
)
// Group represents a user group
type Group struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
OwnerID string `json:"ownerId"`
IsActive bool `json:"isActive"`
CreatedAt string `json:"createdAt"`
}
// Project represents a project
type Project struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
OwnerID string `json:"ownerId"`
IsActive bool `json:"isActive"`
CreatedAt string `json:"createdAt"`
}
// UpdateProfileRequest represents profile update request
type UpdateProfileRequest struct {
FullName string `json:"fullName"`
Email string `json:"email"`
}
// ChangePasswordRequest represents password change request
type ChangePasswordRequest struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
// createUserCommands creates user-related commands
func createUserCommands() *cobra.Command {
userCmd := &cobra.Command{
Use: "user",
Short: "User management commands",
Long: "Commands for managing your user profile and account",
}
profileCmd := &cobra.Command{
Use: "profile",
Short: "View your user profile",
Long: "Display your current user profile information",
RunE: func(cmd *cobra.Command, args []string) error {
return showUserProfile()
},
}
updateCmd := &cobra.Command{
Use: "update",
Short: "Update your user profile",
Long: `Update your user profile information such as full name and email.
You will be prompted for the new values interactively.`,
RunE: func(cmd *cobra.Command, args []string) error {
return updateUserProfile()
},
}
passwordCmd := &cobra.Command{
Use: "password",
Short: "Change your password",
Long: "Change your account password. You will be prompted for the current and new passwords.",
RunE: func(cmd *cobra.Command, args []string) error {
return changePassword()
},
}
groupsCmd := &cobra.Command{
Use: "groups",
Short: "List your groups",
Long: "Display all groups that you are a member of",
RunE: func(cmd *cobra.Command, args []string) error {
return listUserGroups()
},
}
projectsCmd := &cobra.Command{
Use: "projects",
Short: "List your projects",
Long: "Display all projects that you own or have access to",
RunE: func(cmd *cobra.Command, args []string) error {
return listUserProjects()
},
}
userCmd.AddCommand(profileCmd, updateCmd, passwordCmd, groupsCmd, projectsCmd)
return userCmd
}
// showUserProfile displays the user's profile
func showUserProfile() error {
configManager := NewConfigManager()
if !configManager.IsAuthenticated() {
return fmt.Errorf("not authenticated - run 'airavata auth login' first")
}
serverURL, err := configManager.GetServerURL()
if err != nil {
return fmt.Errorf("failed to get server URL: %w", err)
}
token, err := configManager.GetToken()
if err != nil {
return fmt.Errorf("failed to get token: %w", err)
}
user, err := getUserProfile(serverURL, token)
if err != nil {
return fmt.Errorf("failed to get user profile: %w", err)
}
fmt.Println("👤 User Profile")
fmt.Println("===============")
fmt.Printf("ID: %s\n", user.ID)
fmt.Printf("Username: %s\n", user.Username)
fmt.Printf("Full Name: %s\n", user.FullName)
fmt.Printf("Email: %s\n", user.Email)
fmt.Printf("Status: %s\n", getStatusText(user.IsActive))
return nil
}
// updateUserProfile updates the user's profile
func updateUserProfile() error {
configManager := NewConfigManager()
if !configManager.IsAuthenticated() {
return fmt.Errorf("not authenticated - run 'airavata auth login' first")
}
serverURL, err := configManager.GetServerURL()
if err != nil {
return fmt.Errorf("failed to get server URL: %w", err)
}
token, err := configManager.GetToken()
if err != nil {
return fmt.Errorf("failed to get token: %w", err)
}
// Get current profile
currentUser, err := getUserProfile(serverURL, token)
if err != nil {
return fmt.Errorf("failed to get current profile: %w", err)
}
fmt.Println("📝 Update User Profile")
fmt.Println("======================")
fmt.Printf("Current Full Name: %s\n", currentUser.FullName)
fmt.Printf("Current Email: %s\n", currentUser.Email)
fmt.Println()
// Prompt for new values
reader := bufio.NewReader(os.Stdin)
fmt.Print("New Full Name (press Enter to keep current): ")
fullNameInput, _ := reader.ReadString('\n')
fullName := strings.TrimSpace(fullNameInput)
if fullName == "" {
fullName = currentUser.FullName
}
fmt.Print("New Email (press Enter to keep current): ")
emailInput, _ := reader.ReadString('\n')
email := strings.TrimSpace(emailInput)
if email == "" {
email = currentUser.Email
}
// Update profile
updateReq := UpdateProfileRequest{
FullName: fullName,
Email: email,
}
updatedUser, err := updateUserProfileAPI(serverURL, token, updateReq)
if err != nil {
return fmt.Errorf("failed to update profile: %w", err)
}
fmt.Println("✅ Profile updated successfully!")
fmt.Printf("Full Name: %s\n", updatedUser.FullName)
fmt.Printf("Email: %s\n", updatedUser.Email)
return nil
}
// changePassword changes the user's password
func changePassword() error {
configManager := NewConfigManager()
if !configManager.IsAuthenticated() {
return fmt.Errorf("not authenticated - run 'airavata auth login' first")
}
serverURL, err := configManager.GetServerURL()
if err != nil {
return fmt.Errorf("failed to get server URL: %w", err)
}
token, err := configManager.GetToken()
if err != nil {
return fmt.Errorf("failed to get token: %w", err)
}
fmt.Println("🔐 Change Password")
fmt.Println("==================")
// Prompt for current password
fmt.Print("Current Password: ")
currentPassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return fmt.Errorf("failed to read current password: %w", err)
}
fmt.Println()
// Prompt for new password
fmt.Print("New Password: ")
newPassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return fmt.Errorf("failed to read new password: %w", err)
}
fmt.Println()
// Prompt for password confirmation
fmt.Print("Confirm New Password: ")
confirmPassword, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return fmt.Errorf("failed to read password confirmation: %w", err)
}
fmt.Println()
// Validate passwords
if string(newPassword) != string(confirmPassword) {
return fmt.Errorf("passwords do not match")
}
if len(newPassword) < 8 {
return fmt.Errorf("new password must be at least 8 characters long")
}
// Change password
changeReq := ChangePasswordRequest{
OldPassword: string(currentPassword),
NewPassword: string(newPassword),
}
if err := changePasswordAPI(serverURL, token, changeReq); err != nil {
return fmt.Errorf("failed to change password: %w", err)
}
fmt.Println("✅ Password changed successfully!")
return nil
}
// listUserGroups lists the user's groups
func listUserGroups() error {
configManager := NewConfigManager()
if !configManager.IsAuthenticated() {
return fmt.Errorf("not authenticated - run 'airavata auth login' first")
}
serverURL, err := configManager.GetServerURL()
if err != nil {
return fmt.Errorf("failed to get server URL: %w", err)
}
token, err := configManager.GetToken()
if err != nil {
return fmt.Errorf("failed to get token: %w", err)
}
groups, err := getUserGroupsAPI(serverURL, token)
if err != nil {
return fmt.Errorf("failed to get user groups: %w", err)
}
if len(groups) == 0 {
fmt.Println("📋 You are not a member of any groups")
return nil
}
fmt.Printf("📋 Your Groups (%d)\n", len(groups))
fmt.Println("==================")
for _, group := range groups {
fmt.Printf("• %s", group.Name)
if group.Description != "" {
fmt.Printf(" - %s", group.Description)
}
fmt.Printf(" (%s)\n", getStatusText(group.IsActive))
}
return nil
}
// listUserProjects lists the user's projects
func listUserProjects() error {
configManager := NewConfigManager()
if !configManager.IsAuthenticated() {
return fmt.Errorf("not authenticated - run 'airavata auth login' first")
}
serverURL, err := configManager.GetServerURL()
if err != nil {
return fmt.Errorf("failed to get server URL: %w", err)
}
token, err := configManager.GetToken()
if err != nil {
return fmt.Errorf("failed to get token: %w", err)
}
projects, err := getUserProjectsAPI(serverURL, token)
if err != nil {
return fmt.Errorf("failed to get user projects: %w", err)
}
if len(projects) == 0 {
fmt.Println("📁 You don't have any projects")
return nil
}
fmt.Printf("📁 Your Projects (%d)\n", len(projects))
fmt.Println("=====================")
for _, project := range projects {
fmt.Printf("• %s", project.Name)
if project.Description != "" {
fmt.Printf(" - %s", project.Description)
}
fmt.Printf(" (%s)\n", getStatusText(project.IsActive))
}
return nil
}
// updateUserProfileAPI sends profile update request to the server
func updateUserProfileAPI(serverURL, token string, req UpdateProfileRequest) (*User, error) {
jsonData, err := json.Marshal(req)
if err != nil {
return nil, fmt.Errorf("failed to marshal request: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
httpReq, err := http.NewRequestWithContext(ctx, "PUT", serverURL+"/api/v2/user/profile", bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("update failed: %s", string(body))
}
var user User
if err := json.Unmarshal(body, &user); err != nil {
return nil, fmt.Errorf("failed to parse response: %w", err)
}
return &user, nil
}
// changePasswordAPI sends password change request to the server
func changePasswordAPI(serverURL, token string, req ChangePasswordRequest) error {
jsonData, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
httpReq, err := http.NewRequestWithContext(ctx, "PUT", serverURL+"/api/v2/user/password", bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(httpReq)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("password change failed: %s", string(body))
}
return nil
}
// getUserGroupsAPI gets user groups from the server
func getUserGroupsAPI(serverURL, token string) ([]Group, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", serverURL+"/api/v2/user/groups", nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get groups: %s", string(body))
}
var groups []Group
if err := json.Unmarshal(body, &groups); err != nil {
return nil, fmt.Errorf("failed to parse groups: %w", err)
}
return groups, nil
}
// getUserProjectsAPI gets user projects from the server
func getUserProjectsAPI(serverURL, token string) ([]Project, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", serverURL+"/api/v2/user/projects", nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get projects: %s", string(body))
}
var projects []Project
if err := json.Unmarshal(body, &projects); err != nil {
return nil, fmt.Errorf("failed to parse projects: %w", err)
}
return projects, nil
}