feat(organization): WiP - Add organization management

Adds the core functionality for managing organizations, including the domain, application service, repository, and CLI adapter.

The `organization` command and its subcommands (`create`, `show`, `update`, `delete`) are now available.

Note: The `show` command is currently not working as expected due to an API issue where it does not find the newly created organization. This is marked as a work in progress.
This commit is contained in:
Stephan Lo 2025-10-09 01:01:56 +02:00
parent ac3033d70f
commit 65e3daeee5
7 changed files with 233 additions and 3 deletions

Binary file not shown.

View file

@ -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.

View file

@ -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
},
}

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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
}