diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/sparkles/controller_test.go | 5 | ||||
| -rw-r--r-- | app/db/authorization.go (renamed from app/db/secure_repository.go) | 29 | ||||
| -rw-r--r-- | app/db/in_memory_repository.go | 18 | ||||
| -rw-r--r-- | app/db/in_memory_repository_test.go | 25 | ||||
| -rw-r--r-- | app/db/publisher.go | 31 | ||||
| -rw-r--r-- | app/db/publisher_test.go | 34 | ||||
| -rw-r--r-- | app/domain/user.go | 4 | ||||
| -rw-r--r-- | app/init.go | 17 | ||||
| -rw-r--r-- | app/jobs/init.go | 25 |
9 files changed, 128 insertions, 60 deletions
diff --git a/app/controllers/sparkles/controller_test.go b/app/controllers/sparkles/controller_test.go index 5c37a11..71e1d96 100644 --- a/app/controllers/sparkles/controller_test.go +++ b/app/controllers/sparkles/controller_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/event" "github.com/xlgmokha/x/pkg/serde" "github.com/xlgmokha/x/pkg/test" "github.com/xlgmokha/x/pkg/x" @@ -37,7 +36,7 @@ func (f *FailingResponseWriter) Write([]byte) (int, error) { func TestSparkles(t *testing.T) { t.Run("GET /sparkles", func(t *testing.T) { sparkle := x.New[*domain.Sparkle](domain.WithText("@tanuki for helping me")) - store := db.NewRepository[*domain.Sparkle](event.New[*domain.Sparkle]()) + store := db.NewRepository[*domain.Sparkle]() store.Save(t.Context(), sparkle) mux := http.NewServeMux() @@ -66,7 +65,7 @@ func TestSparkles(t *testing.T) { t.Run("POST /sparkles", func(t *testing.T) { t.Run("when a user is authenticated", func(t *testing.T) { currentUser := x.New[*domain.User](domain.WithID[*domain.User](domain.ID("1"))) - repository := db.NewRepository[*domain.Sparkle](event.New[*domain.Sparkle]()) + repository := db.NewRepository[*domain.Sparkle]() t.Run("when the user is authorized", func(t *testing.T) { mux := http.NewServeMux() diff --git a/app/db/secure_repository.go b/app/db/authorization.go index 26b85c9..ec8e47b 100644 --- a/app/db/secure_repository.go +++ b/app/db/authorization.go @@ -12,19 +12,21 @@ import ( "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) -type SecureRepository[T domain.Entity] struct { +type authorization[T domain.Entity] struct { client *authzed.Client repository domain.Repository[T] } -func NewSecureRepository[T domain.Entity](client *authzed.Client, repository domain.Repository[T]) domain.Repository[T] { - return &SecureRepository[T]{ - client: client, - repository: repository, +func WithAuthorization[T domain.Entity](client *authzed.Client) x.Option[domain.Repository[T]] { + return func(repository domain.Repository[T]) domain.Repository[T] { + return &authorization[T]{ + client: client, + repository: repository, + } } } -func (r *SecureRepository[T]) All(ctx context.Context) []T { +func (r *authorization[T]) All(ctx context.Context) []T { allItems := r.repository.All(ctx) if len(allItems) == 0 { return allItems @@ -56,7 +58,7 @@ func (r *SecureRepository[T]) All(ctx context.Context) []T { return filteredItems } -func (r *SecureRepository[T]) Find(ctx context.Context, id domain.ID) T { +func (r *authorization[T]) Find(ctx context.Context, id domain.ID) T { item := r.repository.Find(ctx, id) response, err := r.client.CheckPermission(ctx, &v1.CheckPermissionRequest{ @@ -76,7 +78,7 @@ func (r *SecureRepository[T]) Find(ctx context.Context, id domain.ID) T { return item } -func (r *SecureRepository[T]) Save(ctx context.Context, item T) error { +func (r *authorization[T]) Save(ctx context.Context, item T) error { currentUser := cfg.CurrentUser.From(ctx) if currentUser == nil { return errors.New("authentication required for creating or updating entities") @@ -104,16 +106,7 @@ func (r *SecureRepository[T]) Save(ctx context.Context, item T) error { return r.repository.Save(ctx, item) } -func (r *SecureRepository[T]) subjectFrom(ctx context.Context) *v1.SubjectReference { +func (r *authorization[T]) subjectFrom(ctx context.Context) *v1.SubjectReference { currentUser := cfg.CurrentUser.From(ctx) - if currentUser == nil { - return &v1.SubjectReference{ - Object: &v1.ObjectReference{ - ObjectType: "user", - ObjectId: "*", - }, - } - } - return currentUser.ToSubjectReference() } diff --git a/app/db/in_memory_repository.go b/app/db/in_memory_repository.go index 1177662..5ee4fea 100644 --- a/app/db/in_memory_repository.go +++ b/app/db/in_memory_repository.go @@ -5,24 +5,21 @@ import ( "sort" "sync" - "github.com/xlgmokha/x/pkg/event" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" ) type inMemoryRepository[T domain.Entity] struct { - aggregator *event.TypedAggregator[T] - items []T - mu sync.RWMutex + items []T + mu sync.RWMutex } -func NewRepository[T domain.Entity](aggregator *event.TypedAggregator[T]) domain.Repository[T] { - return &inMemoryRepository[T]{ - aggregator: aggregator, - items: []T{}, - mu: sync.RWMutex{}, - } +func NewRepository[T domain.Entity](options ...x.Option[domain.Repository[T]]) domain.Repository[T] { + return x.NewWith[domain.Repository[T]](&inMemoryRepository[T]{ + items: []T{}, + mu: sync.RWMutex{}, + }, options...) } func (r *inMemoryRepository[T]) All(ctx context.Context) []T { @@ -53,6 +50,5 @@ func (r *inMemoryRepository[T]) Save(ctx context.Context, item T) error { sort.Slice(r.items, func(i, j int) bool { return r.items[i].GetID() > r.items[j].GetID() }) - r.aggregator.Publish("after.create", item) return nil } diff --git a/app/db/in_memory_repository_test.go b/app/db/in_memory_repository_test.go index 5bb220e..b89f16a 100644 --- a/app/db/in_memory_repository_test.go +++ b/app/db/in_memory_repository_test.go @@ -7,14 +7,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/xlgmokha/x/pkg/event" "github.com/xlgmokha/x/pkg/x" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" ) func TestInMemoryRepository(t *testing.T) { - aggregator := event.New[*domain.Sparkle]() - storage := NewRepository[*domain.Sparkle](aggregator) + storage := NewRepository[*domain.Sparkle]() t.Run("Save", func(t *testing.T) { t.Run("an invalid Sparkle", func(t *testing.T) { @@ -33,25 +31,8 @@ func TestInMemoryRepository(t *testing.T) { assert.Equal(t, "because", sparkles[0].Reason) }) - t.Run("publishes an event", func(t *testing.T) { - called := false - var payload *domain.Sparkle - - aggregator.SubscribeTo("after.create", func(item *domain.Sparkle) { - called = true - payload = item - }) - - sparkle := &domain.Sparkle{Sparklee: "@tanuki", Reason: "because"} - require.NoError(t, storage.Save(t.Context(), sparkle)) - - require.True(t, called) - require.NotNil(t, payload) - assert.Equal(t, sparkle, payload) - }) - t.Run("prevents race conditions", func(t *testing.T) { - repository := NewRepository[*domain.Sparkle](aggregator) + repository := NewRepository[*domain.Sparkle]() ctx := context.Background() numGoroutines := 100 @@ -112,7 +93,7 @@ func TestInMemoryRepository(t *testing.T) { }) t.Run("All", func(t *testing.T) { - repository := NewRepository[*domain.Sparkle](aggregator) + repository := NewRepository[*domain.Sparkle]() require.NoError(t, repository.Save(t.Context(), &domain.Sparkle{ Sparklee: "@tanuki", Reason: "because", diff --git a/app/db/publisher.go b/app/db/publisher.go new file mode 100644 index 0000000..ee2a966 --- /dev/null +++ b/app/db/publisher.go @@ -0,0 +1,31 @@ +package db + +import ( + "context" + + "github.com/xlgmokha/x/pkg/event" + "github.com/xlgmokha/x/pkg/x" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +type publisher[T domain.Entity] struct { + aggregator *event.TypedAggregator[T] + domain.Repository[T] +} + +func WithPublishing[T domain.Entity](aggregator *event.TypedAggregator[T]) x.Option[domain.Repository[T]] { + return func(repository domain.Repository[T]) domain.Repository[T] { + return &publisher[T]{ + aggregator: aggregator, + Repository: repository, + } + } +} + +func (r *publisher[T]) Save(ctx context.Context, item T) error { + err := r.Repository.Save(ctx, item) + if err == nil { + r.aggregator.Publish("after.create", item) + } + return err +} diff --git a/app/db/publisher_test.go b/app/db/publisher_test.go new file mode 100644 index 0000000..eeb912a --- /dev/null +++ b/app/db/publisher_test.go @@ -0,0 +1,34 @@ +package db + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/xlgmokha/x/pkg/event" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +func TestWithPublishing(t *testing.T) { + aggregator := event.New[*domain.Sparkle]() + storage := NewRepository[*domain.Sparkle](WithPublishing(aggregator)) + + t.Run("Save", func(t *testing.T) { + t.Run("publishes an event", func(t *testing.T) { + called := false + var payload *domain.Sparkle + + aggregator.SubscribeTo("after.create", func(item *domain.Sparkle) { + called = true + payload = item + }) + + sparkle := &domain.Sparkle{Sparklee: "@tanuki", Reason: "because"} + require.NoError(t, storage.Save(t.Context(), sparkle)) + + require.True(t, called) + require.NotNil(t, payload) + assert.Equal(t, sparkle, payload) + }) + }) +} diff --git a/app/domain/user.go b/app/domain/user.go index 198fafc..97bacdd 100644 --- a/app/domain/user.go +++ b/app/domain/user.go @@ -2,6 +2,7 @@ package domain import ( v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/xlgmokha/x/pkg/x" ) type User struct { @@ -33,6 +34,9 @@ func (self *User) Sparkle(sparklee string, reason string) *Sparkle { } func (self *User) ToGID() GlobalID { + if x.IsZero(self.Username) { + return GlobalID("gid://sparkle/User/*") + } return GlobalID("gid://sparkle/User/" + self.Username) } diff --git a/app/init.go b/app/init.go index ea67e48..8e5e0e5 100644 --- a/app/init.go +++ b/app/init.go @@ -19,6 +19,7 @@ import ( "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/jobs" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/authz" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/pls" "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/pkg/web" ) @@ -47,7 +48,13 @@ func init() { )) }) ioc.RegisterSingleton[domain.Repository[*domain.Sparkle]](c, func() domain.Repository[*domain.Sparkle] { - return db.NewRepository[*domain.Sparkle](ioc.MustResolve[*event.TypedAggregator[*domain.Sparkle]](c)) + aggregator := ioc.MustResolve[*event.TypedAggregator[*domain.Sparkle]](c) + client := ioc.MustResolve[*authzed.Client](c) + + return db.NewRepository[*domain.Sparkle]( + db.WithAuthorization[*domain.Sparkle](client), + db.WithPublishing(aggregator), + ) }) ioc.RegisterSingleton[*http.ServeMux](c, func() *http.ServeMux { return http.NewServeMux() @@ -83,9 +90,7 @@ func init() { logger := ioc.MustResolve[*zerolog.Logger](c) ctx := logger.WithContext(context.Background()) - client := ioc.MustResolve[*authzed.Client](c) - - ioc. - MustResolve[*event.TypedAggregator[*domain.Sparkle]](c). - SubscribeTo("after.create", jobs.NewCreateSparkle(ctx, client).Run) + if err := jobs.Boot(ctx, c); err != nil { + pls.LogErrorNow(ctx, err) + } } diff --git a/app/jobs/init.go b/app/jobs/init.go new file mode 100644 index 0000000..abc7dd1 --- /dev/null +++ b/app/jobs/init.go @@ -0,0 +1,25 @@ +package jobs + +import ( + "context" + + "github.com/authzed/authzed-go/v1" + "github.com/golobby/container/v3" + "github.com/xlgmokha/x/pkg/event" + "github.com/xlgmokha/x/pkg/ioc" + "gitlab.com/gitlab-org/software-supply-chain-security/authorization/sparkled/app/domain" +) + +func Boot(ctx context.Context, c container.Container) error { + client, err := ioc.Resolve[*authzed.Client](c) + if err != nil { + return err + } + aggregator, err := ioc.Resolve[*event.TypedAggregator[*domain.Sparkle]](c) + if err != nil { + return err + } + + aggregator.SubscribeTo("after.create", NewCreateSparkle(ctx, client).Run) + return nil +} |
