package domain import ( "context" "fmt" "strings" "gitlab.com/unboundsoftware/eventsourced/eventsourced" "gitea.unbound.se/unboundsoftware/schemas/hash" ) type AddOrganization struct { Name string Initiator string } func (a AddOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { if aggregate.Identity() != nil { return fmt.Errorf("organization already exists") } if len(a.Name) == 0 { return fmt.Errorf("name is required") } return nil } func (a AddOrganization) Event(context.Context) eventsourced.Event { return &OrganizationAdded{ Name: a.Name, Initiator: a.Initiator, } } var _ eventsourced.Command = AddOrganization{} type AddUserToOrganization struct { UserId string Initiator string } func (a AddUserToOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { if aggregate.Identity() == nil { return fmt.Errorf("organization does not exist") } if len(a.UserId) == 0 { return fmt.Errorf("userId is required") } // Check if user is already in the organization org := aggregate.(*Organization) for _, user := range org.Users { if user == a.UserId { return fmt.Errorf("user is already a member of this organization") } } return nil } func (a AddUserToOrganization) Event(context.Context) eventsourced.Event { return &UserAddedToOrganization{ UserId: a.UserId, Initiator: a.Initiator, } } var _ eventsourced.Command = AddUserToOrganization{} type AddAPIKey struct { Name string Key string Refs []string Read bool Publish bool Initiator string } func (a AddAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { if aggregate.Identity() == nil { return fmt.Errorf("organization does not exist") } for _, k := range aggregate.(*Organization).APIKeys { if k.Name == a.Name { return fmt.Errorf("a key named '%s' already exist", a.Name) } } return nil } func (a AddAPIKey) Event(context.Context) eventsourced.Event { // Hash the API key using bcrypt for secure storage // Note: We can't return an error here, but bcrypt errors are extremely rare // (only if system runs out of memory or bcrypt cost is invalid) // We use a fixed cost of 12 which is always valid hashedKey, err := hash.APIKey(a.Key) if err != nil { // This should never happen with bcrypt cost 12, but if it does, // we'll store an empty hash which will fail validation later hashedKey = "" } return &APIKeyAdded{ Name: a.Name, Key: hashedKey, Refs: a.Refs, Read: a.Read, Publish: a.Publish, Initiator: a.Initiator, } } var _ eventsourced.Command = AddAPIKey{} type RemoveAPIKey struct { KeyName string Initiator string } func (r RemoveAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { if aggregate.Identity() == nil { return fmt.Errorf("organization does not exist") } org := aggregate.(*Organization) found := false for _, k := range org.APIKeys { if k.Name == r.KeyName { found = true break } } if !found { return fmt.Errorf("API key '%s' not found", r.KeyName) } return nil } func (r RemoveAPIKey) Event(context.Context) eventsourced.Event { return &APIKeyRemoved{ KeyName: r.KeyName, Initiator: r.Initiator, } } var _ eventsourced.Command = RemoveAPIKey{} type RemoveOrganization struct { Initiator string } func (r RemoveOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { if aggregate.Identity() == nil { return fmt.Errorf("organization does not exist") } return nil } func (r RemoveOrganization) Event(context.Context) eventsourced.Event { return &OrganizationRemoved{ Initiator: r.Initiator, } } var _ eventsourced.Command = RemoveOrganization{} type UpdateSubGraph struct { OrganizationId string Ref string Service string Url *string WSUrl *string Sdl string Initiator string } func (u UpdateSubGraph) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { switch a := aggregate.(type) { case *SubGraph: if strings.TrimSpace(u.Ref) == "" { return fmt.Errorf("ref is missing") } if strings.TrimSpace(u.Service) == "" { return fmt.Errorf("service is missing") } if strings.TrimSpace(u.Sdl) == "" { return fmt.Errorf("SDL is missing") } if (u.Url == nil || strings.TrimSpace(*u.Url) == "") && a.Url == nil { return fmt.Errorf("url is missing") } default: return fmt.Errorf("aggregate is not a SubGraph") } return nil } func (u UpdateSubGraph) Event(context.Context) eventsourced.Event { return &SubGraphUpdated{ OrganizationId: u.OrganizationId, Ref: u.Ref, Service: u.Service, Url: u.Url, WSUrl: u.WSUrl, Sdl: u.Sdl, Initiator: u.Initiator, } } var _ eventsourced.Command = UpdateSubGraph{}