From 5a8c60a5aabbae2fd3b4e5c147cbdf7c7799b113 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Fri, 13 Feb 2026 09:02:05 +0100 Subject: [PATCH] Add webhooks type Signed-off-by: Sacha Al Himdani --- pkg/probo/meeting_service.go | 9 ++-- pkg/probo/vendor_service.go | 8 ++-- pkg/webhook/data.go | 90 +----------------------------------- pkg/webhook/sender.go | 1 + pkg/webhook/types/meeting.go | 42 +++++++++++++++++ pkg/webhook/types/vendor.go | 74 +++++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 97 deletions(-) create mode 100644 pkg/webhook/types/meeting.go create mode 100644 pkg/webhook/types/vendor.go diff --git a/pkg/probo/meeting_service.go b/pkg/probo/meeting_service.go index eb5827396..6f7e25a74 100644 --- a/pkg/probo/meeting_service.go +++ b/pkg/probo/meeting_service.go @@ -23,9 +23,10 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/server/api/console/v1/types" "go.probo.inc/probo/pkg/validator" "go.probo.inc/probo/pkg/webhook" + + webhooktypes "go.probo.inc/probo/pkg/webhook/types" ) type MeetingService struct { @@ -209,7 +210,7 @@ func (s MeetingService) Create( } } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, organization.ID, coredata.WebhookEventTypeMeetingCreated, types.NewMeeting(meeting)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, organization.ID, coredata.WebhookEventTypeMeetingCreated, webhooktypes.NewMeeting(meeting)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } @@ -289,7 +290,7 @@ func (s MeetingService) Update( } } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingUpdated, types.NewMeeting(meeting)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingUpdated, webhooktypes.NewMeeting(meeting)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } @@ -317,7 +318,7 @@ func (s MeetingService) Delete( return fmt.Errorf("cannot load meeting: %w", err) } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingDeleted, types.NewMeeting(meeting)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, meeting.OrganizationID, coredata.WebhookEventTypeMeetingDeleted, webhooktypes.NewMeeting(meeting)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } diff --git a/pkg/probo/vendor_service.go b/pkg/probo/vendor_service.go index 154dd50e0..c729d78ac 100644 --- a/pkg/probo/vendor_service.go +++ b/pkg/probo/vendor_service.go @@ -23,9 +23,9 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.probo.inc/probo/pkg/server/api/console/v1/types" "go.probo.inc/probo/pkg/validator" "go.probo.inc/probo/pkg/webhook" + webhooktypes "go.probo.inc/probo/pkg/webhook/types" ) type ( @@ -394,7 +394,7 @@ func (s VendorService) Update( return fmt.Errorf("cannot update vendor: %w", err) } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, vendor.OrganizationID, coredata.WebhookEventTypeVendorUpdated, types.NewVendor(vendor)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, vendor.OrganizationID, coredata.WebhookEventTypeVendorUpdated, webhooktypes.NewVendor(vendor)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } @@ -442,7 +442,7 @@ func (s VendorService) Delete( return fmt.Errorf("cannot load vendor: %w", err) } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, vendor.OrganizationID, coredata.WebhookEventTypeVendorDeleted, types.NewVendor(vendor)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, vendor.OrganizationID, coredata.WebhookEventTypeVendorDeleted, webhooktypes.NewVendor(vendor)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } @@ -519,7 +519,7 @@ func (s VendorService) Create( return fmt.Errorf("cannot insert vendor: %w", err) } - if err := webhook.InsertEvent(ctx, conn, s.svc.scope, organization.ID, coredata.WebhookEventTypeVendorCreated, types.NewVendor(vendor)); err != nil { + if err := webhook.InsertEvent(ctx, conn, s.svc.scope, organization.ID, coredata.WebhookEventTypeVendorCreated, webhooktypes.NewVendor(vendor)); err != nil { return fmt.Errorf("cannot insert webhook event: %w", err) } diff --git a/pkg/webhook/data.go b/pkg/webhook/data.go index b8d0fa5db..06d6791e8 100644 --- a/pkg/webhook/data.go +++ b/pkg/webhook/data.go @@ -16,11 +16,8 @@ package webhook import ( "context" - "encoding" "encoding/json" "fmt" - "reflect" - "strings" "time" "go.gearno.de/kit/pg" @@ -28,11 +25,6 @@ import ( "go.probo.inc/probo/pkg/gid" ) -var ( - jsonMarshalerType = reflect.TypeOf((*json.Marshaler)(nil)).Elem() - textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() -) - func InsertEvent( ctx context.Context, conn pg.Conn, @@ -51,7 +43,7 @@ func InsertEvent( return nil } - raw, err := MarshalData(data) + raw, err := json.Marshal(data) if err != nil { return fmt.Errorf("cannot marshal webhook event data: %w", err) } @@ -71,83 +63,3 @@ func InsertEvent( return nil } - -func MarshalData(v any) (json.RawMessage, error) { - raw, err := json.Marshal(v) - if err != nil { - return nil, fmt.Errorf("cannot marshal webhook data: %w", err) - } - - var m map[string]any - if err := json.Unmarshal(raw, &m); err != nil { - return nil, fmt.Errorf("cannot unmarshal webhook data: %w", err) - } - - for _, key := range nestedFieldKeys(v) { - delete(m, key) - } - - delete(m, "permission") - - data, err := json.Marshal(m) - if err != nil { - return nil, fmt.Errorf("cannot re-marshal webhook data: %w", err) - } - - return data, nil -} - -func nestedFieldKeys(v any) []string { - t := reflect.TypeOf(v) - for t.Kind() == reflect.Pointer { - t = t.Elem() - } - - if t.Kind() != reflect.Struct { - return nil - } - - var keys []string - for i := range t.NumField() { - field := t.Field(i) - - tag := field.Tag.Get("json") - if tag == "" || tag == "-" { - continue - } - - jsonKey, _, _ := strings.Cut(tag, ",") - - if isNestedType(field.Type) { - keys = append(keys, jsonKey) - } - } - - return keys -} - -func isNestedType(t reflect.Type) bool { - for t.Kind() == reflect.Pointer { - t = t.Elem() - } - - if t.Kind() == reflect.Slice { - return isNestedType(t.Elem()) - } - - if t.Kind() != reflect.Struct { - return false - } - - ptrType := reflect.PointerTo(t) - - if t.Implements(jsonMarshalerType) || ptrType.Implements(jsonMarshalerType) { - return false - } - - if t.Implements(textMarshalerType) || ptrType.Implements(textMarshalerType) { - return false - } - - return true -} diff --git a/pkg/webhook/sender.go b/pkg/webhook/sender.go index ec8b581ce..1fb064772 100644 --- a/pkg/webhook/sender.go +++ b/pkg/webhook/sender.go @@ -227,6 +227,7 @@ func (s *Sender) doHTTPCall( "eventId": event.ID.String(), "callId": callID.String(), "configurationId": configurationID.String(), + "organizationId": event.OrganizationID.String(), "eventType": event.EventType.String(), "createdAt": event.CreatedAt, "data": event.Data, diff --git a/pkg/webhook/types/meeting.go b/pkg/webhook/types/meeting.go new file mode 100644 index 000000000..9ec766f7e --- /dev/null +++ b/pkg/webhook/types/meeting.go @@ -0,0 +1,42 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "time" + + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" +) + +type Meeting struct { + ID gid.GID `json:"id"` + Name string `json:"name"` + Date time.Time `json:"date"` + Minutes *string `json:"minutes"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func NewMeeting(m *coredata.Meeting) *Meeting { + return &Meeting{ + ID: m.ID, + Name: m.Name, + Date: m.Date, + Minutes: m.Minutes, + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + } +} diff --git a/pkg/webhook/types/vendor.go b/pkg/webhook/types/vendor.go new file mode 100644 index 000000000..886b5630a --- /dev/null +++ b/pkg/webhook/types/vendor.go @@ -0,0 +1,74 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "time" + + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" +) + +type Vendor struct { + ID gid.GID `json:"id"` + Name string `json:"name"` + Category coredata.VendorCategory `json:"category"` + Description *string `json:"description"` + StatusPageURL *string `json:"statusPageUrl"` + TermsOfServiceURL *string `json:"termsOfServiceUrl"` + PrivacyPolicyURL *string `json:"privacyPolicyUrl"` + ServiceLevelAgreementURL *string `json:"serviceLevelAgreementUrl"` + DataProcessingAgreementURL *string `json:"dataProcessingAgreementUrl"` + BusinessAssociateAgreementURL *string `json:"businessAssociateAgreementUrl"` + SubprocessorsListURL *string `json:"subprocessorsListUrl"` + Certifications []string `json:"certifications"` + Countries []coredata.CountryCode `json:"countries"` + SecurityPageURL *string `json:"securityPageUrl"` + TrustPageURL *string `json:"trustPageUrl"` + HeadquarterAddress *string `json:"headquarterAddress"` + LegalName *string `json:"legalName"` + WebsiteURL *string `json:"websiteUrl"` + BusinessOwnerID *gid.GID `json:"businessOwnerId"` + SecurityOwnerID *gid.GID `json:"securityOwnerId"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func NewVendor(v *coredata.Vendor) *Vendor { + return &Vendor{ + ID: v.ID, + Name: v.Name, + Category: v.Category, + Description: v.Description, + StatusPageURL: v.StatusPageURL, + TermsOfServiceURL: v.TermsOfServiceURL, + PrivacyPolicyURL: v.PrivacyPolicyURL, + ServiceLevelAgreementURL: v.ServiceLevelAgreementURL, + DataProcessingAgreementURL: v.DataProcessingAgreementURL, + BusinessAssociateAgreementURL: v.BusinessAssociateAgreementURL, + SubprocessorsListURL: v.SubprocessorsListURL, + Certifications: v.Certifications, + Countries: v.Countries, + SecurityPageURL: v.SecurityPageURL, + TrustPageURL: v.TrustPageURL, + HeadquarterAddress: v.HeadquarterAddress, + LegalName: v.LegalName, + WebsiteURL: v.WebsiteURL, + BusinessOwnerID: v.BusinessOwnerID, + SecurityOwnerID: v.SecurityOwnerID, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + } +}