82 lines
3.2 KiB
Go
82 lines
3.2 KiB
Go
// Package model holds the wire and domain types passed between layers.
|
|
package model
|
|
|
|
import (
|
|
"encoding/json"
|
|
"time"
|
|
)
|
|
|
|
// EventType is the Segment-compatible call kind.
|
|
type EventType string
|
|
|
|
const (
|
|
EventTypeTrack EventType = "track"
|
|
EventTypeIdentify EventType = "identify"
|
|
EventTypePage EventType = "page"
|
|
EventTypeGroup EventType = "group"
|
|
EventTypeAlias EventType = "alias"
|
|
EventTypeScreen EventType = "screen"
|
|
)
|
|
|
|
// RawEvent is the parsed-but-not-yet-validated payload from a client.
|
|
// We keep Properties / Traits / Context as json.RawMessage so the handler can
|
|
// pass them through to the service untouched; flattening happens in service.
|
|
type RawEvent struct {
|
|
Type EventType `json:"type" validate:"required,oneof=track identify page group alias screen"`
|
|
MessageID string `json:"messageId" validate:"required,max=128"`
|
|
AnonymousID string `json:"anonymousId" validate:"max=128"`
|
|
UserID string `json:"userId" validate:"max=128"`
|
|
GroupID string `json:"groupId" validate:"max=128"`
|
|
Event string `json:"event" validate:"max=255"`
|
|
Name string `json:"name" validate:"max=255"`
|
|
Category string `json:"category" validate:"max=255"`
|
|
Properties json.RawMessage `json:"properties"`
|
|
Traits json.RawMessage `json:"traits"`
|
|
Context json.RawMessage `json:"context"`
|
|
Timestamp *time.Time `json:"timestamp"`
|
|
SentAt *time.Time `json:"sentAt"`
|
|
}
|
|
|
|
// BatchEnvelope is the body of /batch — Segment-compatible.
|
|
type BatchEnvelope struct {
|
|
Batch []RawEvent `json:"batch" validate:"required,min=1,max=1000,dive"`
|
|
SentAt *time.Time `json:"sentAt"`
|
|
Context json.RawMessage `json:"context"`
|
|
}
|
|
|
|
// IngestedEvent is the canonical record we push onto Kafka. Flat fields,
|
|
// timestamps already normalized, payload sanitized.
|
|
type IngestedEvent struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
SourceID string `json:"source_id"`
|
|
MessageID string `json:"message_id"`
|
|
Type EventType `json:"type"`
|
|
AnonymousID string `json:"anonymous_id,omitempty"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
GroupID string `json:"group_id,omitempty"`
|
|
Event string `json:"event,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Category string `json:"category,omitempty"`
|
|
Properties map[string]any `json:"properties,omitempty"`
|
|
Traits map[string]any `json:"traits,omitempty"`
|
|
Context map[string]any `json:"context,omitempty"`
|
|
IP string `json:"ip,omitempty"`
|
|
UserAgent string `json:"user_agent,omitempty"`
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
SentAt time.Time `json:"sent_at"`
|
|
ReceivedAt time.Time `json:"received_at"`
|
|
}
|
|
|
|
// PartitionKey returns the key used for Kafka partitioning. We use
|
|
// anonymous_id to keep identity-stitching ordering per visitor.
|
|
func (e *IngestedEvent) PartitionKey() string {
|
|
if e.AnonymousID != "" {
|
|
return e.AnonymousID
|
|
}
|
|
if e.UserID != "" {
|
|
return e.UserID
|
|
}
|
|
return e.MessageID
|
|
}
|