From 27b2d906e32944c3d3004293818b2519de4a5f2e Mon Sep 17 00:00:00 2001 From: IsaacDSC Date: Wed, 18 Feb 2026 13:46:56 -0300 Subject: [PATCH 1/2] Refactor consumer handling: rename CreateConsumer to CreateOrUpdateConsumer, update repository methods to use Upsert, and remove unused path event handling code. --- Makefile | 2 +- cmd/setup/backoffice/httpserver.go | 3 +- internal/backoffice/interfaces.go | 2 +- internal/backoffice/path_event_handle.go | 50 ------ internal/backoffice/path_event_handle_test.go | 155 ------------------ internal/backoffice/register_consumer.go | 12 +- .../register_event_consumer_archived.go | 4 +- internal/backoffice/repository_mock.go | 28 ++-- internal/interstore/postgres_store.go | 13 +- internal/interstore/store.go | 2 +- 10 files changed, 38 insertions(+), 233 deletions(-) delete mode 100644 internal/backoffice/path_event_handle.go delete mode 100644 internal/backoffice/path_event_handle_test.go diff --git a/Makefile b/Makefile index 0212c9b..3ff1554 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,7 @@ generate-mocks: install-mockgen clean-mocks @echo "$(BLUE)Gerando mock para Cache...$(NC)" @$(MOCKGEN) -source=pkg/cachemanager/adapter.go -destination=pkg/cachemanager/cache_mock.go -package=cachemanager @echo "$(BLUE)Gerando mock para Backoffice Repository...$(NC)" - @$(MOCKGEN) -source=internal/backoffice/interfaces.go -destination=internal/backoffice/repository_mock.go -package=backoffice + @$(MOCKGEN) -source=internal/backoffice/register_consumer.go -destination=internal/backoffice/repository_mock.go -package=backoffice @echo "$(BLUE)Gerando mock para Publisher...$(NC)" @$(MOCKGEN) -source=pkg/pubadapter/adapter.go -destination=pkg/publisher/publisher_task_mock.go -package=publisher @echo "$(BLUE)Gerando mock para Publisher em pubadapter...$(NC)" diff --git a/cmd/setup/backoffice/httpserver.go b/cmd/setup/backoffice/httpserver.go index 2976c5c..79dfbac 100644 --- a/cmd/setup/backoffice/httpserver.go +++ b/cmd/setup/backoffice/httpserver.go @@ -29,9 +29,8 @@ func Start( routes := []httpadapter.HttpHandle{ backoffice.GetHealthCheckHandler(), - backoffice.CreateConsumer(cache, store), + backoffice.CreateOrUpdateConsumer(cache, store), backoffice.GetEvent(cache, store), - backoffice.GetPathEventHandle(cache, store), backoffice.GetEvents(cache, store), backoffice.GetRegisterTaskConsumerArchived(cache, store), backoffice.RemoveEvent(cache, store), diff --git a/internal/backoffice/interfaces.go b/internal/backoffice/interfaces.go index 7861845..00afa9b 100644 --- a/internal/backoffice/interfaces.go +++ b/internal/backoffice/interfaces.go @@ -8,7 +8,7 @@ import ( ) type Repository interface { - Save(ctx context.Context, event domain.Event) error + Upsert(ctx context.Context, event domain.Event) error GetInternalEvent(ctx context.Context, eventName string) (domain.Event, error) GetInternalEvents(ctx context.Context, filters domain.FilterEvents) ([]domain.Event, error) DisabledEvent(ctx context.Context, eventID uuid.UUID) error diff --git a/internal/backoffice/path_event_handle.go b/internal/backoffice/path_event_handle.go deleted file mode 100644 index 05364cd..0000000 --- a/internal/backoffice/path_event_handle.go +++ /dev/null @@ -1,50 +0,0 @@ -package backoffice - -import ( - "context" - "encoding/json" - "net/http" - - "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" - "github.com/IsaacDSC/gqueue/pkg/httpadapter" - "github.com/google/uuid" -) - -func GetPathEventHandle(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { - return httpadapter.HttpHandle{ - Path: "PATCH /api/v1/event/{id}", - Handler: func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - id, err := uuid.Parse(r.PathValue("id")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - defer r.Body.Close() - var payload domain.Event - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - payload.ID = id - - key := eventKey(cc, payload.ServiceName, payload.Name) - defaultTTL := cc.GetDefaultTTL() - - if err := cc.Hydrate(ctx, key, &payload, defaultTTL, func(ctx context.Context) (any, error) { - return payload, repo.UpdateEvent(ctx, payload) - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if err := json.NewEncoder(w).Encode(payload); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }, - } -} diff --git a/internal/backoffice/path_event_handle_test.go b/internal/backoffice/path_event_handle_test.go deleted file mode 100644 index 1e76b79..0000000 --- a/internal/backoffice/path_event_handle_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package backoffice - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/IsaacDSC/gqueue/internal/domain" - "github.com/IsaacDSC/gqueue/pkg/cachemanager" - "github.com/google/uuid" - "github.com/stretchr/testify/assert" - "go.uber.org/mock/gomock" -) - -func TestGetPathEventHandle(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - validUUID := uuid.New() - - tests := []struct { - name string - eventID string - payload domain.Event - setupMocks func(*MockRepository, *cachemanager.MockCache) - expectedStatus int - }{ - { - name: "success_update", - eventID: validUUID.String(), - payload: domain.Event{ - Name: "user.updated", - ServiceName: "user-service", - State: "active", - Type: "external", - Option: domain.Opt{ - MaxRetries: 3, - }, - Consumers: []domain.Consumer{ - { - ServiceName: "notification-service", - Host: "https://api.example.com", - Path: "/webhook", - Headers: map[string]string{"Content-Type": "application/json"}, - }, - }, - }, - setupMocks: func(mockRepo *MockRepository, mockCache *cachemanager.MockCache) { - key := cachemanager.Key("event-queue:user-service:user.updated") - ttl := 5 * time.Minute - - mockCache.EXPECT().Key(domain.CacheKeyEventPrefix, "user-service", "user.updated").Return(key) - mockCache.EXPECT().GetDefaultTTL().Return(ttl) - mockCache.EXPECT().Hydrate( - gomock.Any(), - key, - gomock.Any(), - ttl, - gomock.Any(), - ).DoAndReturn(func(ctx context.Context, key cachemanager.Key, value any, ttl time.Duration, fn cachemanager.Fn) error { - _, err := fn(ctx) - return err - }) - - mockRepo.EXPECT().UpdateEvent(gomock.Any(), gomock.Any()).Return(nil) - }, - expectedStatus: http.StatusOK, - }, - { - name: "redis_error", - eventID: validUUID.String(), - payload: domain.Event{ - Name: "user.created", - ServiceName: "user-service", - State: "active", - }, - setupMocks: func(mockRepo *MockRepository, mockCache *cachemanager.MockCache) { - key := cachemanager.Key("event-queue:user-service:user.created") - ttl := 5 * time.Minute - - mockCache.EXPECT().Key(domain.CacheKeyEventPrefix, "user-service", "user.created").Return(key) - mockCache.EXPECT().GetDefaultTTL().Return(ttl) - mockCache.EXPECT().Hydrate( - gomock.Any(), - key, - gomock.Any(), - ttl, - gomock.Any(), - ).Return(errors.New("redis connection failed")) - }, - expectedStatus: http.StatusInternalServerError, - }, - { - name: "database_error", - eventID: validUUID.String(), - payload: domain.Event{ - Name: "order.processed", - ServiceName: "order-service", - State: "active", - }, - setupMocks: func(mockRepo *MockRepository, mockCache *cachemanager.MockCache) { - key := cachemanager.Key("event-queue:order-service:order.processed") - ttl := 5 * time.Minute - - mockCache.EXPECT().Key(domain.CacheKeyEventPrefix, "order-service", "order.processed").Return(key) - mockCache.EXPECT().GetDefaultTTL().Return(ttl) - mockCache.EXPECT().Hydrate( - gomock.Any(), - key, - gomock.Any(), - ttl, - gomock.Any(), - ).DoAndReturn(func(ctx context.Context, key cachemanager.Key, value any, ttl time.Duration, fn cachemanager.Fn) error { - _, err := fn(ctx) - return err - }) - - mockRepo.EXPECT().UpdateEvent(gomock.Any(), gomock.Any()).Return(errors.New("database connection failed")) - }, - expectedStatus: http.StatusInternalServerError, - }, - { - name: "invalid_uuid", - eventID: "invalid-uuid", - payload: domain.Event{}, - setupMocks: func(mockRepo *MockRepository, mockCache *cachemanager.MockCache) {}, - expectedStatus: http.StatusBadRequest, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - mockRepo := NewMockRepository(ctrl) - mockCache := cachemanager.NewMockCache(ctrl) - - tt.setupMocks(mockRepo, mockCache) - - handle := GetPathEventHandle(mockCache, mockRepo) - - payloadBytes, _ := json.Marshal(tt.payload) - req := httptest.NewRequest(http.MethodPatch, "/api/v1/event/"+tt.eventID, bytes.NewReader(payloadBytes)) - req.SetPathValue("id", tt.eventID) - - rr := httptest.NewRecorder() - handle.Handler(rr, req) - - assert.Equal(t, tt.expectedStatus, rr.Code) - }) - } -} diff --git a/internal/backoffice/register_consumer.go b/internal/backoffice/register_consumer.go index c3283c8..d2894fc 100644 --- a/internal/backoffice/register_consumer.go +++ b/internal/backoffice/register_consumer.go @@ -33,7 +33,7 @@ func (e *EventDto) ToDomain() domain.Event { } } -func CreateConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func CreateOrUpdateConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ Path: "POST /api/v1/event/consumer", Handler: func(w http.ResponseWriter, r *http.Request) { @@ -55,8 +55,8 @@ func CreateConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHand defaultTTL := cc.GetDefaultTTL() if err := cc.Hydrate(ctx, key, &payload, defaultTTL, func(ctx context.Context) (any, error) { - if err := repo.Save(ctx, event); err != nil { - return domain.Event{}, fmt.Errorf("failed to create internal event: %w", err) + if err := repo.Upsert(ctx, event); err != nil { + return domain.Event{}, fmt.Errorf("failed to upsert internal event: %w", err) } return payload, nil }); err != nil { @@ -64,7 +64,11 @@ func CreateConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHand return } - w.WriteHeader(http.StatusCreated) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if err := json.NewEncoder(w).Encode(payload); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } }, } } diff --git a/internal/backoffice/register_event_consumer_archived.go b/internal/backoffice/register_event_consumer_archived.go index 3b1f040..f74b220 100644 --- a/internal/backoffice/register_event_consumer_archived.go +++ b/internal/backoffice/register_event_consumer_archived.go @@ -30,8 +30,8 @@ func GetRegisterTaskConsumerArchived(cc cachemanager.Cache, repo Repository) htt key := cc.Key(typeEvent, payload.State, payload.ServiceName, payload.Name) if err := cc.Hydrate(ctx, key, &payload, cc.GetDefaultTTL(), func(ctx context.Context) (any, error) { - if err := repo.Save(ctx, payload); err != nil { - return domain.Event{}, fmt.Errorf("failed to create internal event: %w", err) + if err := repo.Upsert(ctx, payload); err != nil { + return domain.Event{}, fmt.Errorf("failed to upsert internal event: %w", err) } return payload, nil }); err != nil { diff --git a/internal/backoffice/repository_mock.go b/internal/backoffice/repository_mock.go index 24cfacb..02d866c 100644 --- a/internal/backoffice/repository_mock.go +++ b/internal/backoffice/repository_mock.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: internal/backoffice/interfaces.go +// Source: internal/backoffice/register_consumer.go // // Generated by this command: // -// mockgen -source=internal/backoffice/interfaces.go -destination=internal/backoffice/repository_mock.go -package=backoffice +// mockgen -source=internal/backoffice/register_consumer.go -destination=internal/backoffice/repository_mock.go -package=backoffice // // Package backoffice is a generated GoMock package. @@ -101,30 +101,30 @@ func (mr *MockRepositoryMockRecorder) GetInternalEvents(ctx, filters any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInternalEvents", reflect.TypeOf((*MockRepository)(nil).GetInternalEvents), ctx, filters) } -// Save mocks base method. -func (m *MockRepository) Save(ctx context.Context, event domain.Event) error { +// UpdateEvent mocks base method. +func (m *MockRepository) UpdateEvent(ctx context.Context, event domain.Event) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Save", ctx, event) + ret := m.ctrl.Call(m, "UpdateEvent", ctx, event) ret0, _ := ret[0].(error) return ret0 } -// Save indicates an expected call of Save. -func (mr *MockRepositoryMockRecorder) Save(ctx, event any) *gomock.Call { +// UpdateEvent indicates an expected call of UpdateEvent. +func (mr *MockRepositoryMockRecorder) UpdateEvent(ctx, event any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockRepository)(nil).Save), ctx, event) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvent", reflect.TypeOf((*MockRepository)(nil).UpdateEvent), ctx, event) } -// UpdateEvent mocks base method. -func (m *MockRepository) UpdateEvent(ctx context.Context, event domain.Event) error { +// Upsert mocks base method. +func (m *MockRepository) Upsert(ctx context.Context, event domain.Event) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateEvent", ctx, event) + ret := m.ctrl.Call(m, "Upsert", ctx, event) ret0, _ := ret[0].(error) return ret0 } -// UpdateEvent indicates an expected call of UpdateEvent. -func (mr *MockRepositoryMockRecorder) UpdateEvent(ctx, event any) *gomock.Call { +// Upsert indicates an expected call of Upsert. +func (mr *MockRepositoryMockRecorder) Upsert(ctx, event any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEvent", reflect.TypeOf((*MockRepository)(nil).UpdateEvent), ctx, event) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockRepository)(nil).Upsert), ctx, event) } diff --git a/internal/interstore/postgres_store.go b/internal/interstore/postgres_store.go index 2273b21..5aa8b18 100644 --- a/internal/interstore/postgres_store.go +++ b/internal/interstore/postgres_store.go @@ -161,7 +161,7 @@ func (r *PostgresStore) GetInternalEvents(ctx context.Context, filters domain.Fi return events, nil } -func (r *PostgresStore) Save(ctx context.Context, event domain.Event) error { +func (r *PostgresStore) Upsert(ctx context.Context, event domain.Event) error { l := ctxlogger.GetLogger(ctx) consumersJSON, err := json.Marshal(event.Consumers) @@ -181,6 +181,13 @@ func (r *PostgresStore) Save(ctx context.Context, event domain.Event) error { query := ` INSERT INTO events (id, name, service_name, state, consumers, opts, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) + ON CONFLICT (name) + DO UPDATE SET + service_name = EXCLUDED.service_name, + state = EXCLUDED.state, + consumers = EXCLUDED.consumers, + opts = EXCLUDED.opts, + updated_at = EXCLUDED.updated_at ` now := time.Now() @@ -196,8 +203,8 @@ func (r *PostgresStore) Save(ctx context.Context, event domain.Event) error { ) if err != nil { - l.Error("Error on create internal event", "error", err) - return fmt.Errorf("failed to create internal event: %w", err) + l.Error("Error on upsert internal event", "error", err) + return fmt.Errorf("failed to upsert internal event: %w", err) } return nil diff --git a/internal/interstore/store.go b/internal/interstore/store.go index 8e6392c..bf8fd23 100644 --- a/internal/interstore/store.go +++ b/internal/interstore/store.go @@ -8,7 +8,7 @@ import ( ) type Repository interface { - Save(ctx context.Context, event domain.Event) error + Upsert(ctx context.Context, event domain.Event) error GetInternalEvent(ctx context.Context, eventName string) (domain.Event, error) GetInternalEvents(ctx context.Context, filters domain.FilterEvents) ([]domain.Event, error) DisabledEvent(ctx context.Context, eventID uuid.UUID) error From 2e81e53581541570f4b6e73833b88078d0617647 Mon Sep 17 00:00:00 2001 From: IsaacDSC Date: Thu, 19 Feb 2026 11:22:35 -0300 Subject: [PATCH 2/2] Refactor consumer handling: rename CreateOrUpdateConsumer to PatchConsumer, update HTTP method to PATCH, and enhance error logging in consumer registration. --- cmd/setup/backoffice/httpserver.go | 2 +- cmd/setup/middleware/middleware.go | 2 ++ example/example.md | 13 ++----------- internal/backoffice/register_consumer.go | 16 ++++++++++------ 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cmd/setup/backoffice/httpserver.go b/cmd/setup/backoffice/httpserver.go index 79dfbac..9511f28 100644 --- a/cmd/setup/backoffice/httpserver.go +++ b/cmd/setup/backoffice/httpserver.go @@ -29,7 +29,7 @@ func Start( routes := []httpadapter.HttpHandle{ backoffice.GetHealthCheckHandler(), - backoffice.CreateOrUpdateConsumer(cache, store), + backoffice.PatchConsumer(cache, store), backoffice.GetEvent(cache, store), backoffice.GetEvents(cache, store), backoffice.GetRegisterTaskConsumerArchived(cache, store), diff --git a/cmd/setup/middleware/middleware.go b/cmd/setup/middleware/middleware.go index 96bae16..f836e6c 100644 --- a/cmd/setup/middleware/middleware.go +++ b/cmd/setup/middleware/middleware.go @@ -96,6 +96,8 @@ func CORSMiddlewareWithConfig(config CORSConfig) func(http.Handler) http.Handler return } + w.Header().Set("Content-Type", "application/json") + next.ServeHTTP(w, r) }) } diff --git a/example/example.md b/example/example.md index b5e029a..b163776 100644 --- a/example/example.md +++ b/example/example.md @@ -1,6 +1,6 @@ -### Example create event +### Example create or update event -curl -X POST \ +curl -X PATCH \ http://localhost:8080/api/v1/event/consumer \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ @@ -40,12 +40,3 @@ curl -i -X DELETE \ curl -X GET \ -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ=" \ http://localhost:8080/api/v1/insights | jq - -### PATCH event - -curl -X PATCH \ - 'http://localhost:8080/api/v1/event/da4543c5-3cca-4151-8737-5f4cf7fa702f' \ - -H "Content-Type: application/json" \ - -H "Accept: application/json" \ - -H "Authorization: Basic YWRtaW46cGFzc3dvcmQ=" \ - -d @example/path_event_data.json diff --git a/internal/backoffice/register_consumer.go b/internal/backoffice/register_consumer.go index d2894fc..191c76f 100644 --- a/internal/backoffice/register_consumer.go +++ b/internal/backoffice/register_consumer.go @@ -9,6 +9,7 @@ import ( "github.com/IsaacDSC/gqueue/internal/cfg" "github.com/IsaacDSC/gqueue/internal/domain" "github.com/IsaacDSC/gqueue/pkg/cachemanager" + "github.com/IsaacDSC/gqueue/pkg/ctxlogger" "github.com/IsaacDSC/gqueue/pkg/httpadapter" "github.com/google/uuid" ) @@ -33,10 +34,13 @@ func (e *EventDto) ToDomain() domain.Event { } } -func CreateOrUpdateConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { +func PatchConsumer(cc cachemanager.Cache, repo Repository) httpadapter.HttpHandle { return httpadapter.HttpHandle{ - Path: "POST /api/v1/event/consumer", + Path: "PATCH /api/v1/event/consumer", Handler: func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + l := ctxlogger.GetLogger(ctx) + var payload EventDto defer r.Body.Close() if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { @@ -50,7 +54,6 @@ func CreateOrUpdateConsumer(cc cachemanager.Cache, repo Repository) httpadapter. return } - ctx := r.Context() key := eventKey(cc, event.ServiceName, event.Name) defaultTTL := cc.GetDefaultTTL() @@ -60,14 +63,15 @@ func CreateOrUpdateConsumer(cc cachemanager.Cache, repo Repository) httpadapter. } return payload, nil }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + l.Error("failed to save consumer", "error", err) + http.Error(w, "failed to save consumer", http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(payload); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + l.Error("failed to encode response", "error", err) + http.Error(w, "failed to encode response", http.StatusInternalServerError) } }, }