+notification from site +version bump fix
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful

This commit is contained in:
Stephan D
2025-11-17 22:20:17 +01:00
parent c6a56071b5
commit 9dbf77a9a8
21 changed files with 543 additions and 9 deletions

View File

@@ -0,0 +1,47 @@
package notifications
import (
"fmt"
gmessaging "github.com/tech/sendico/pkg/generated/gmessaging"
messaging "github.com/tech/sendico/pkg/messaging/envelope"
"github.com/tech/sendico/pkg/model"
nm "github.com/tech/sendico/pkg/model/notification"
"github.com/tech/sendico/pkg/mservice"
"google.golang.org/protobuf/proto"
)
type DemoRequestNotification struct {
messaging.Envelope
request *model.DemoRequest
}
func (drn *DemoRequestNotification) Serialize() ([]byte, error) {
if drn.request == nil {
return nil, fmt.Errorf("demo request payload is empty")
}
msg := gmessaging.DemoRequestEvent{
Name: drn.request.Name,
OrganizationName: drn.request.OrganizationName,
Phone: drn.request.Phone,
WorkEmail: drn.request.WorkEmail,
PayoutVolume: drn.request.PayoutVolume,
Comment: drn.request.Comment,
}
data, err := proto.Marshal(&msg)
if err != nil {
return nil, err
}
return drn.Envelope.Wrap(data)
}
func NewDemoRequestEvent() model.NotificationEvent {
return model.NewNotification(mservice.Site, nm.NACreated)
}
func NewDemoRequestEnvelope(sender string, request *model.DemoRequest) messaging.Envelope {
return &DemoRequestNotification{
Envelope: messaging.CreateEnvelope(sender, NewDemoRequestEvent()),
request: request,
}
}

View File

@@ -0,0 +1,11 @@
package notifications
import (
messaging "github.com/tech/sendico/pkg/messaging/envelope"
internalsite "github.com/tech/sendico/pkg/messaging/internal/notifications/site"
"github.com/tech/sendico/pkg/model"
)
func DemoRequest(sender string, request *model.DemoRequest) messaging.Envelope {
return internalsite.NewDemoRequestEnvelope(sender, request)
}

View File

@@ -0,0 +1,9 @@
package notifications
import (
"context"
"github.com/tech/sendico/pkg/model"
)
type DemoRequestHandler = func(context.Context, *model.DemoRequest) error

View File

@@ -0,0 +1,50 @@
package notifications
import (
"context"
gmessaging "github.com/tech/sendico/pkg/generated/gmessaging"
me "github.com/tech/sendico/pkg/messaging/envelope"
internalsite "github.com/tech/sendico/pkg/messaging/internal/notifications/site"
np "github.com/tech/sendico/pkg/messaging/notifications/processor"
handler "github.com/tech/sendico/pkg/messaging/notifications/site/handler"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
)
type DemoRequestProcessor struct {
logger mlogger.Logger
handler handler.DemoRequestHandler
event model.NotificationEvent
}
func (drp *DemoRequestProcessor) Process(ctx context.Context, envelope me.Envelope) error {
var msg gmessaging.DemoRequestEvent
if err := proto.Unmarshal(envelope.GetData(), &msg); err != nil {
drp.logger.Warn("Failed to decode demo request envelope", zap.Error(err), zap.String("topic", drp.event.ToString()))
return err
}
request := &model.DemoRequest{
Name: msg.GetName(),
OrganizationName: msg.GetOrganizationName(),
Phone: msg.GetPhone(),
WorkEmail: msg.GetWorkEmail(),
PayoutVolume: msg.GetPayoutVolume(),
Comment: msg.GetComment(),
}
return drp.handler(ctx, request)
}
func (drp *DemoRequestProcessor) GetSubject() model.NotificationEvent {
return drp.event
}
func NewDemoRequestProcessor(logger mlogger.Logger, handler handler.DemoRequestHandler) np.EnvelopeProcessor {
return &DemoRequestProcessor{
logger: logger.Named("demo_request_processor"),
handler: handler,
event: internalsite.NewDemoRequestEvent(),
}
}

View File

@@ -0,0 +1,53 @@
package model
import (
"strings"
"github.com/tech/sendico/pkg/merrors"
)
// DemoRequest represents a request submitted from the marketing site to request a demo.
type DemoRequest struct {
Name string `json:"name"`
OrganizationName string `json:"organizationName"`
Phone string `json:"phone"`
WorkEmail string `json:"workEmail"`
PayoutVolume string `json:"payoutVolume"`
Comment string `json:"comment,omitempty"`
}
// Normalize trims whitespace from all string fields.
func (dr *DemoRequest) Normalize() {
if dr == nil {
return
}
dr.Name = strings.TrimSpace(dr.Name)
dr.OrganizationName = strings.TrimSpace(dr.OrganizationName)
dr.Phone = strings.TrimSpace(dr.Phone)
dr.WorkEmail = strings.TrimSpace(dr.WorkEmail)
dr.PayoutVolume = strings.TrimSpace(dr.PayoutVolume)
dr.Comment = strings.TrimSpace(dr.Comment)
}
// Validate ensures that all required fields are present.
func (dr *DemoRequest) Validate() error {
if dr == nil {
return merrors.InvalidArgument("request payload is empty")
}
if dr.Name == "" {
return merrors.InvalidArgument("name must not be empty")
}
if dr.OrganizationName == "" {
return merrors.InvalidArgument("organization name must not be empty")
}
if dr.Phone == "" {
return merrors.InvalidArgument("phone must not be empty")
}
if dr.WorkEmail == "" {
return merrors.InvalidArgument("work email must not be empty")
}
if dr.PayoutVolume == "" {
return merrors.InvalidArgument("payout volume must not be empty")
}
return nil
}

View File

@@ -0,0 +1,31 @@
package model
import "testing"
func TestDemoRequestNormalizeAndValidate(t *testing.T) {
req := &DemoRequest{
Name: " Alice ",
OrganizationName: " Sendico ",
Phone: " +1 234 ",
WorkEmail: " demo@sendico.io ",
PayoutVolume: " 100k ",
Comment: " Excited ",
}
req.Normalize()
if err := req.Validate(); err != nil {
t.Fatalf("expected request to be valid, got error: %v", err)
}
if req.Name != "Alice" || req.OrganizationName != "Sendico" || req.Phone != "+1 234" || req.WorkEmail != "demo@sendico.io" || req.PayoutVolume != "100k" || req.Comment != "Excited" {
t.Fatalf("normalize failed: %+v", req)
}
}
func TestDemoRequestValidateMissing(t *testing.T) {
req := &DemoRequest{}
req.Normalize()
if err := req.Validate(); err == nil {
t.Fatalf("expected validation error for empty request")
}
}

View File

@@ -7,6 +7,7 @@ type Type = string
const (
Accounts Type = "accounts" // Represents user accounts in the system
Amplitude Type = "amplitude" // Represents analytics integration with Amplitude
Site Type = "site" // Represents public site endpoints
Automations Type = "automation" // Represents automation workflows
Changes Type = "changes" // Tracks changes made to resources
Clients Type = "clients" // Represents client information
@@ -59,7 +60,7 @@ const (
func StringToSType(s string) (Type, error) {
switch Type(s) {
case Accounts, Amplitude, Automations, Changes, Clients, Comments, ChainGateway, ChainWallets, ChainWalletBalances,
case Accounts, Amplitude, Site, Automations, Changes, Clients, Comments, ChainGateway, ChainWallets, ChainWalletBalances,
ChainTransfers, ChainDeposits, FXOracle, FeePlans, FilterProjects, Invitations, Invoices, Logo, Ledger,
LedgerAccounts, LedgerBalances, LedgerEntries, LedgerOutbox, LedgerParties, LedgerPlines, Notifications,
Organizations, Payments, PaymentOrchestrator, Permissions, Policies, PolicyAssignements, Priorities,