diff --git a/bin/edge-connect-cli b/bin/edge-connect-cli index 775e545..84230e1 100755 Binary files a/bin/edge-connect-cli and b/bin/edge-connect-cli differ diff --git a/internal/adapters/driven/edgeconnect/adapter.go b/internal/adapters/driven/edgeconnect/adapter.go index e07abbc..1b3a7c9 100644 --- a/internal/adapters/driven/edgeconnect/adapter.go +++ b/internal/adapters/driven/edgeconnect/adapter.go @@ -50,14 +50,23 @@ func (a *Adapter) CreateOrganization(ctx context.Context, org *domain.Organizati func (a *Adapter) ShowOrganization(ctx context.Context, name string) (*domain.Organization, error) { apiPath := "/api/v1/auth/org/show" reqBody := map[string]string{"name": name} - var org domain.Organization + var orgs []domain.Organization - _, err := a.client.Call(ctx, http.MethodPost, apiPath, reqBody, &org) + _, err := a.client.Call(ctx, http.MethodPost, apiPath, reqBody, &orgs) if err != nil { // TODO: Improve error handling, check for 404 and return domain.ErrResourceNotFound return nil, fmt.Errorf("failed to show organization %s: %w", name, err) } - return &org, nil + + if len(orgs) == 0 { + return nil, fmt.Errorf("organization '%s' not found", name) + } + + if len(orgs) > 1 { + a.client.Logf("warning: ShowOrganization for '%s' returned %d results, expected 1", name, len(orgs)) + } + + return &orgs[0], nil } // UpdateOrganization updates an existing organization. diff --git a/internal/adapters/driving/cli/organization.go b/internal/adapters/driving/cli/organization.go new file mode 100644 index 0000000..cb0e376 --- /dev/null +++ b/internal/adapters/driving/cli/organization.go @@ -0,0 +1,113 @@ +package cli + +import ( + "context" + "fmt" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(organizationCmd) + organizationCmd.AddCommand(createOrganizationCmd, showOrganizationCmd, updateOrganizationCmd, deleteOrganizationCmd) + + // Flags for create/update + createOrganizationCmd.Flags().StringVar(&orgAddress, "address", "", "Address of the organization") + createOrganizationCmd.Flags().StringVar(&orgPhone, "phone", "", "Phone number of the organization") + + updateOrganizationCmd.Flags().StringVar(&orgAddress, "address", "", "New address for the organization") + updateOrganizationCmd.Flags().StringVar(&orgPhone, "phone", "", "New phone number for the organization") +} + +var ( + orgName string + orgAddress string + orgPhone string +) + +// organizationCmd represents the parent command for all organization-related actions. +var organizationCmd = &cobra.Command{ + Use: "organization", + Short: "Manage organizations", + Long: `Create, show, update, and delete organizations in Edge Connect.`, + Aliases: []string{"org"}, +} + +var createOrganizationCmd = &cobra.Command{ + Use: "create [name]", + Short: "Create a new organization", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + org := &domain.Organization{ + Name: args[0], + Address: orgAddress, + Phone: orgPhone, + } + err := services.OrganizationService.Create(context.Background(), org) + if err != nil { + return fmt.Errorf("Error creating organization: %w", err) + } + fmt.Printf("Organization '%s' created successfully.\n", args[0]) + return nil + }, +} + +var showOrganizationCmd = &cobra.Command{ + Use: "show [name]", + Short: "Show details of an organization", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + org, err := services.OrganizationService.Get(context.Background(), args[0]) + if err != nil { + return fmt.Errorf("Error showing organization: %w", err) + } + fmt.Printf("Organization Details:\n") + fmt.Printf(" Name: %s\n", org.Name) + fmt.Printf(" Address: %s\n", org.Address) + fmt.Printf(" Phone: %s\n", org.Phone) + return nil + }, +} + +var updateOrganizationCmd = &cobra.Command{ + Use: "update [name]", + Short: "Update an organization", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + // First, get the current organization to update + org, err := services.OrganizationService.Get(context.Background(), args[0]) + if err != nil { + return fmt.Errorf("could not retrieve organization to update: %w", err) + } + + // Update fields if flags were provided + if cmd.Flags().Changed("address") { + org.Address = orgAddress + } + if cmd.Flags().Changed("phone") { + org.Phone = orgPhone + } + + err = services.OrganizationService.Update(context.Background(), org) + if err != nil { + return fmt.Errorf("Error updating organization: %w", err) + } + fmt.Printf("Organization '%s' updated successfully.\n", args[0]) + return nil + }, +} + +var deleteOrganizationCmd = &cobra.Command{ + Use: "delete [name]", + Short: "Delete an organization", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + err := services.OrganizationService.Delete(context.Background(), args[0]) + if err != nil { + return fmt.Errorf("Error deleting organization: %w", err) + } + fmt.Printf("Organization '%s' deleted successfully.\n", args[0]) + return nil + }, +} diff --git a/internal/application/organization/service.go b/internal/application/organization/service.go new file mode 100644 index 0000000..e77f8b2 --- /dev/null +++ b/internal/application/organization/service.go @@ -0,0 +1,63 @@ +package organization + +import ( + "context" + "strings" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/ports/driven" + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/ports/driving" +) + +// service implements the OrganizationService interface and provides the core business logic. +type service struct { + repo driven.OrganizationRepository +} + +// NewService creates a new organization service with the given repository. +func NewService(repo driven.OrganizationRepository) driving.OrganizationService { + return &service{repo: repo} +} + +// CreateOrganization validates the organization and passes it to the repository for creation. +func (s *service) Create(ctx context.Context, org *domain.Organization) error { + if err := s.validateOrganization(org); err != nil { + return err + } + return s.repo.CreateOrganization(ctx, org) +} + +// Get retrieves an organization by name. +func (s *service) Get(ctx context.Context, name string) (*domain.Organization, error) { + if strings.TrimSpace(name) == "" { + return nil, domain.NewDomainError(domain.ErrValidationFailed, "organization name cannot be empty") + } + return s.repo.ShowOrganization(ctx, name) +} + +// Update validates the organization and passes it to the repository for updates. +func (s *service) Update(ctx context.Context, org *domain.Organization) error { + if err := s.validateOrganization(org); err != nil { + return err + } + return s.repo.UpdateOrganization(ctx, org) +} + +// Delete removes an organization by name. +func (s *service) Delete(ctx context.Context, name string) error { + if strings.TrimSpace(name) == "" { + return domain.NewDomainError(domain.ErrValidationFailed, "organization name cannot be empty") + } + return s.repo.DeleteOrganization(ctx, name) +} + +// validateOrganization contains the business logic for validating an organization. +func (s *service) validateOrganization(org *domain.Organization) error { + if org == nil { + return domain.NewDomainError(domain.ErrValidationFailed, "organization cannot be nil") + } + if strings.TrimSpace(org.Name) == "" { + return domain.NewDomainError(domain.ErrValidationFailed, "organization name cannot be empty") + } + return nil +} diff --git a/internal/core/domain/organization.go b/internal/core/domain/organization.go new file mode 100644 index 0000000..34b2c5b --- /dev/null +++ b/internal/core/domain/organization.go @@ -0,0 +1,9 @@ +package domain + +// Organization represents the core business object for an organization. +// It contains identifying information such as name, address, and phone number. +type Organization struct { + Name string `json:"name"` + Address string `json:"address"` + Phone string `json:"phone"` +} diff --git a/internal/core/ports/driven/organization_repository.go b/internal/core/ports/driven/organization_repository.go new file mode 100644 index 0000000..5828d07 --- /dev/null +++ b/internal/core/ports/driven/organization_repository.go @@ -0,0 +1,20 @@ +package driven + +import ( + "context" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain" +) + +// OrganizationRepository defines the port for interacting with organization data storage. +// This interface provides a technology-agnostic way for the core application to manage organizations. +type OrganizationRepository interface { + // CreateOrganization persists a new organization. + CreateOrganization(ctx context.Context, org *domain.Organization) error + // ShowOrganization retrieves a single organization by its name. + ShowOrganization(ctx context.Context, name string) (*domain.Organization, error) + // UpdateOrganization updates an existing organization. + UpdateOrganization(ctx context.Context, org *domain.Organization) error + // DeleteOrganization removes an organization by its name. + DeleteOrganization(ctx context.Context, name string) error +} diff --git a/internal/core/ports/driving/organization_service.go b/internal/core/ports/driving/organization_service.go new file mode 100644 index 0000000..d46d3ea --- /dev/null +++ b/internal/core/ports/driving/organization_service.go @@ -0,0 +1,16 @@ +package driving + +import ( + "context" + + "edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/domain" +) + +// OrganizationService defines the driving port for managing organizations. +// This is the primary interface for interacting with the application's organization logic. +type OrganizationService interface { + Create(ctx context.Context, org *domain.Organization) error + Get(ctx context.Context, name string) (*domain.Organization, error) + Update(ctx context.Context, org *domain.Organization) error + Delete(ctx context.Context, name string) error +}