@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<164efe490915ac140a48e42cbd17e915>>
|
* @generated SignedSource<<72029a8e627a0b60cb1e2c226af0d535>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "USER_CREATED" | "USER_DELETED" | "USER_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
||||||
export type WebhooksSettingsPageQuery$variables = {
|
export type WebhooksSettingsPageQuery$variables = {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<240851e53a2871e13a6e19a5b327684c>>
|
* @generated SignedSource<<310671de1c8bca322875bfca9102fcbf>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "USER_CREATED" | "USER_DELETED" | "USER_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
||||||
export type CreateWebhookSubscriptionInput = {
|
export type CreateWebhookSubscriptionInput = {
|
||||||
endpointUrl: string;
|
endpointUrl: string;
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<864603efd6135475b827a1234b128b7c>>
|
* @generated SignedSource<<c58f6047e14212818972c5cf56162c1c>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
export type WebhookEventType = "MEETING_CREATED" | "MEETING_DELETED" | "MEETING_UPDATED" | "USER_CREATED" | "USER_DELETED" | "USER_UPDATED" | "VENDOR_CREATED" | "VENDOR_DELETED" | "VENDOR_UPDATED";
|
||||||
export type UpdateWebhookSubscriptionInput = {
|
export type UpdateWebhookSubscriptionInput = {
|
||||||
endpointUrl?: string | null | undefined;
|
endpointUrl?: string | null | undefined;
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -147,6 +147,9 @@ const EVENT_TYPES = [
|
|||||||
{ value: "VENDOR_CREATED", label: "vendor:created" },
|
{ value: "VENDOR_CREATED", label: "vendor:created" },
|
||||||
{ value: "VENDOR_UPDATED", label: "vendor:updated" },
|
{ value: "VENDOR_UPDATED", label: "vendor:updated" },
|
||||||
{ value: "VENDOR_DELETED", label: "vendor:deleted" },
|
{ value: "VENDOR_DELETED", label: "vendor:deleted" },
|
||||||
|
{ value: "USER_CREATED", label: "user:created" },
|
||||||
|
{ value: "USER_UPDATED", label: "user:updated" },
|
||||||
|
{ value: "USER_DELETED", label: "user:deleted" },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type WebhookEventType = (typeof EVENT_TYPES)[number]["value"];
|
type WebhookEventType = (typeof EVENT_TYPES)[number]["value"];
|
||||||
|
|||||||
3
pkg/coredata/migrations/20260224T120000Z.sql
Normal file
3
pkg/coredata/migrations/20260224T120000Z.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TYPE webhook_event_type ADD VALUE 'user:created';
|
||||||
|
ALTER TYPE webhook_event_type ADD VALUE 'user:updated';
|
||||||
|
ALTER TYPE webhook_event_type ADD VALUE 'user:deleted';
|
||||||
@@ -29,6 +29,9 @@ const (
|
|||||||
WebhookEventTypeVendorCreated WebhookEventType = "vendor:created"
|
WebhookEventTypeVendorCreated WebhookEventType = "vendor:created"
|
||||||
WebhookEventTypeVendorUpdated WebhookEventType = "vendor:updated"
|
WebhookEventTypeVendorUpdated WebhookEventType = "vendor:updated"
|
||||||
WebhookEventTypeVendorDeleted WebhookEventType = "vendor:deleted"
|
WebhookEventTypeVendorDeleted WebhookEventType = "vendor:deleted"
|
||||||
|
WebhookEventTypeUserCreated WebhookEventType = "user:created"
|
||||||
|
WebhookEventTypeUserUpdated WebhookEventType = "user:updated"
|
||||||
|
WebhookEventTypeUserDeleted WebhookEventType = "user:deleted"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w WebhookEventType) String() string {
|
func (w WebhookEventType) String() string {
|
||||||
@@ -38,7 +41,8 @@ func (w WebhookEventType) String() string {
|
|||||||
func (w WebhookEventType) IsValid() bool {
|
func (w WebhookEventType) IsValid() bool {
|
||||||
switch w {
|
switch w {
|
||||||
case WebhookEventTypeMeetingCreated, WebhookEventTypeMeetingUpdated, WebhookEventTypeMeetingDeleted,
|
case WebhookEventTypeMeetingCreated, WebhookEventTypeMeetingUpdated, WebhookEventTypeMeetingDeleted,
|
||||||
WebhookEventTypeVendorCreated, WebhookEventTypeVendorUpdated, WebhookEventTypeVendorDeleted:
|
WebhookEventTypeVendorCreated, WebhookEventTypeVendorUpdated, WebhookEventTypeVendorDeleted,
|
||||||
|
WebhookEventTypeUserCreated, WebhookEventTypeUserUpdated, WebhookEventTypeUserDeleted:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/slug"
|
"go.probo.inc/probo/pkg/slug"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
"go.probo.inc/probo/pkg/validator"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
|
"go.probo.inc/probo/pkg/webhook"
|
||||||
|
webhooktypes "go.probo.inc/probo/pkg/webhook/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -320,6 +322,10 @@ func (s *OrganizationService) RemoveUser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := webhook.InsertData(ctx, tx, scope, organizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(&profile)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := profile.Delete(ctx, tx, scope, profileID); err != nil {
|
if err := profile.Delete(ctx, tx, scope, profileID); err != nil {
|
||||||
return fmt.Errorf("cannot delete profile: %w", err)
|
return fmt.Errorf("cannot delete profile: %w", err)
|
||||||
}
|
}
|
||||||
@@ -920,6 +926,10 @@ func (s *OrganizationService) CreateUser(ctx context.Context, req *CreateUserReq
|
|||||||
return fmt.Errorf("cannot insert membership: %w", err)
|
return fmt.Errorf("cannot insert membership: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := webhook.InsertData(ctx, conn, scope, req.OrganizationID, coredata.WebhookEventTypeUserCreated, webhooktypes.NewUser(profile)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -941,7 +951,7 @@ func (s *OrganizationService) UpdateUser(ctx context.Context, req *UpdateUserReq
|
|||||||
profile = &coredata.MembershipProfile{}
|
profile = &coredata.MembershipProfile{}
|
||||||
)
|
)
|
||||||
|
|
||||||
err := s.pg.WithConn(
|
err := s.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
if err := profile.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
if err := profile.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
||||||
@@ -967,6 +977,10 @@ func (s *OrganizationService) UpdateUser(ctx context.Context, req *UpdateUserReq
|
|||||||
return fmt.Errorf("cannot update profile: %w", err)
|
return fmt.Errorf("cannot update profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := webhook.InsertData(ctx, conn, scope, profile.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/mail"
|
"go.probo.inc/probo/pkg/mail"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/webhook"
|
||||||
|
webhooktypes "go.probo.inc/probo/pkg/webhook/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -174,6 +176,7 @@ func (s *Service) CreateUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if profile exists
|
// Check if profile exists
|
||||||
|
eventType := coredata.WebhookEventTypeUserUpdated
|
||||||
profile = &coredata.MembershipProfile{}
|
profile = &coredata.MembershipProfile{}
|
||||||
if err := profile.LoadByIdentityIDAndOrganizationID(
|
if err := profile.LoadByIdentityIDAndOrganizationID(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -187,6 +190,7 @@ func (s *Service) CreateUser(
|
|||||||
ID: gid.New(config.OrganizationID.TenantID(), coredata.MembershipProfileEntityType),
|
ID: gid.New(config.OrganizationID.TenantID(), coredata.MembershipProfileEntityType),
|
||||||
IdentityID: identity.ID,
|
IdentityID: identity.ID,
|
||||||
OrganizationID: config.OrganizationID,
|
OrganizationID: config.OrganizationID,
|
||||||
|
EmailAddress: emailAddr,
|
||||||
Source: coredata.ProfileSourceSCIM,
|
Source: coredata.ProfileSourceSCIM,
|
||||||
State: profileState,
|
State: profileState,
|
||||||
FullName: fullName,
|
FullName: fullName,
|
||||||
@@ -199,6 +203,7 @@ func (s *Service) CreateUser(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot insert profile: %w", err)
|
return fmt.Errorf("cannot insert profile: %w", err)
|
||||||
}
|
}
|
||||||
|
eventType = coredata.WebhookEventTypeUserCreated
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
@@ -257,6 +262,10 @@ func (s *Service) CreateUser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, eventType, webhooktypes.NewUser(profile)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -509,6 +518,10 @@ func (s *Service) updateUser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile)); err != nil {
|
||||||
|
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2282,6 +2282,12 @@ enum WebhookEventType
|
|||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorUpdated")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorUpdated")
|
||||||
VENDOR_DELETED
|
VENDOR_DELETED
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorDeleted")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeVendorDeleted")
|
||||||
|
USER_CREATED
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserCreated")
|
||||||
|
USER_UPDATED
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserUpdated")
|
||||||
|
USER_DELETED
|
||||||
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookEventTypeUserDeleted")
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebhookSubscription implements Node {
|
type WebhookSubscription implements Node {
|
||||||
|
|||||||
57
pkg/webhook/types/user.go
Normal file
57
pkg/webhook/types/user.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// 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"
|
||||||
|
"go.probo.inc/probo/pkg/mail"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID gid.GID `json:"id"`
|
||||||
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
|
EmailAddress mail.Addr `json:"emailAddress"`
|
||||||
|
FullName string `json:"fullName"`
|
||||||
|
Kind coredata.MembershipProfileKind `json:"kind"`
|
||||||
|
Source coredata.ProfileSource `json:"source"`
|
||||||
|
State coredata.ProfileState `json:"state"`
|
||||||
|
AdditionalEmailAddresses mail.Addrs `json:"additionalEmailAddresses"`
|
||||||
|
Position *string `json:"position"`
|
||||||
|
ContractStartDate *time.Time `json:"contractStartDate"`
|
||||||
|
ContractEndDate *time.Time `json:"contractEndDate"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUser(p *coredata.MembershipProfile) *User {
|
||||||
|
return &User{
|
||||||
|
ID: p.ID,
|
||||||
|
OrganizationID: p.OrganizationID,
|
||||||
|
EmailAddress: p.EmailAddress,
|
||||||
|
FullName: p.FullName,
|
||||||
|
Kind: p.Kind,
|
||||||
|
Source: p.Source,
|
||||||
|
State: p.State,
|
||||||
|
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
|
||||||
|
Position: p.Position,
|
||||||
|
ContractStartDate: p.ContractStartDate,
|
||||||
|
ContractEndDate: p.ContractEndDate,
|
||||||
|
CreatedAt: p.CreatedAt,
|
||||||
|
UpdatedAt: p.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user