Files
argoyle 73eae98929
schemas / vulnerabilities (pull_request) Successful in 2m15s
schemas / check-release (pull_request) Successful in 2m17s
schemas / check (pull_request) Successful in 4m48s
pre-commit / pre-commit (pull_request) Successful in 5m58s
schemas / build (pull_request) Successful in 3m36s
schemas / deploy-prod (pull_request) Has been skipped
feat: migrate from GitLab CI to Gitea Actions
- Update git remote to git.unbound.se
- Add Gitea workflows: ci.yaml, pre-commit.yaml, release.yaml, goreleaser.yaml
- Delete .gitlab-ci.yml
- Update Go module path to gitea.unbound.se/unboundsoftware/schemas
- Update all imports to new module path
- Update Docker registry to oci.unbound.se
- Update .goreleaser.yml for Gitea releases with internal cluster URL
- Remove GitLab CI linter from pre-commit config
- Use shared release workflow with tag_only for versioning

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 22:53:46 +01:00

208 lines
4.8 KiB
Go

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