ffcf41b85a
Introduce `AddUserToOrganization`, `RemoveAPIKey`, and `RemoveOrganization` commands to enhance organization management. Implement validation for user addition and API key removal. Update GraphQL schema to support new mutations and add caching for the new events, ensuring that organizations and their relationships are accurately represented in the cache.
208 lines
4.8 KiB
Go
208 lines
4.8 KiB
Go
package domain
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
|
|
|
"gitlab.com/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{}
|