Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package dispatch
|
||||
|
||||
// Event types and helper functions for the enhanced dispatch system.
|
||||
//
|
||||
// Reference: Chatwoot event naming conventions
|
||||
// - Chatwoot uses Wisper-style event names like "conversation.created",
|
||||
// "message.created", "contact.created", etc.
|
||||
// - In gochat, channel.EventType already defines these constants.
|
||||
// - This file provides additional event types specific to the dispatch layer
|
||||
// and helper functions for event construction.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
)
|
||||
|
||||
// DispatchEvent defines additional event types specific to the dispatch layer.
|
||||
// These complement the channel.EventType constants already defined in
|
||||
// channel/event.go.
|
||||
type DispatchEvent string
|
||||
|
||||
const (
|
||||
// Auto-assignment events (triggered by autoassignment listener)
|
||||
DispatchEventAutoAssign DispatchEvent = "auto_assignment.assign"
|
||||
DispatchEventAutoAssignFailed DispatchEvent = "auto_assignment.assign_failed"
|
||||
DispatchEventAutoAssignSkipped DispatchEvent = "auto_assignment.assign_skipped"
|
||||
|
||||
// Reporting events (for analytics and reporting)
|
||||
DispatchEventReportGenerated DispatchEvent = "report.generated"
|
||||
DispatchEventReportFailed DispatchEvent = "report.failed"
|
||||
|
||||
// CSAT survey events
|
||||
DispatchEventCsatSurveySent DispatchEvent = "csat_survey.sent"
|
||||
DispatchEventCsatResponseReceived DispatchEvent = "csat_survey.response_received"
|
||||
|
||||
// Campaign events
|
||||
DispatchEventCampaignStarted DispatchEvent = "campaign.started"
|
||||
DispatchEventCampaignCompleted DispatchEvent = "campaign.completed"
|
||||
|
||||
// Webhook/hook events
|
||||
DispatchEventHookTriggered DispatchEvent = "hook.triggered"
|
||||
DispatchEventWebhookSent DispatchEvent = "webhook.sent"
|
||||
DispatchEventWebhookFailed DispatchEvent = "webhook.failed"
|
||||
)
|
||||
|
||||
// NewChannelEvent creates a ChannelEvent with the given type and data.
|
||||
// Timestamp is set to the current Unix milliseconds.
|
||||
func NewChannelEvent(eventType string, data map[string]interface{}) *channel.ChannelEvent {
|
||||
if data == nil {
|
||||
data = make(map[string]interface{})
|
||||
}
|
||||
return &channel.ChannelEvent{
|
||||
Type: channel.EventType(eventType),
|
||||
Data: data,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewDispatchEvent creates a ChannelEvent from a DispatchEvent type.
|
||||
func NewDispatchEvent(eventType DispatchEvent, data map[string]interface{}) *channel.ChannelEvent {
|
||||
return NewChannelEvent(string(eventType), data)
|
||||
}
|
||||
|
||||
// EventDataWithConversation creates event data containing a conversation ID.
|
||||
// This is a common pattern where events need to reference a specific conversation.
|
||||
func EventDataWithConversation(conversationID uint, accountID uint, inboxID uint) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"conversation_id": conversationID,
|
||||
"account_id": accountID,
|
||||
"inbox_id": inboxID,
|
||||
}
|
||||
}
|
||||
|
||||
// EventDataWithAssignment creates event data for an assignment event,
|
||||
// including the agent who was assigned.
|
||||
func EventDataWithAssignment(conversationID uint, accountID uint, inboxID uint, assigneeID uint) map[string]interface{} {
|
||||
data := EventDataWithConversation(conversationID, accountID, inboxID)
|
||||
data["assignee_id"] = assigneeID
|
||||
return data
|
||||
}
|
||||
|
||||
// ExtractConversationID extracts a conversation ID from event data.
|
||||
// Returns 0 and an error if not found or not a valid uint.
|
||||
func ExtractConversationID(data map[string]interface{}) (uint, error) {
|
||||
raw, ok := data["conversation_id"]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("conversation_id not found in event data")
|
||||
}
|
||||
switch v := raw.(type) {
|
||||
case uint:
|
||||
return v, nil
|
||||
case int:
|
||||
return uint(v), nil
|
||||
case float64:
|
||||
return uint(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected type for conversation_id: %T", raw)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractAccountID extracts an account ID from event data.
|
||||
func ExtractAccountID(data map[string]interface{}) (uint, error) {
|
||||
raw, ok := data["account_id"]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("account_id not found in event data")
|
||||
}
|
||||
switch v := raw.(type) {
|
||||
case uint:
|
||||
return v, nil
|
||||
case int:
|
||||
return uint(v), nil
|
||||
case float64:
|
||||
return uint(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected type for account_id: %T", raw)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractInboxID extracts an inbox ID from event data.
|
||||
func ExtractInboxID(data map[string]interface{}) (uint, error) {
|
||||
raw, ok := data["inbox_id"]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("inbox_id not found in event data")
|
||||
}
|
||||
switch v := raw.(type) {
|
||||
case uint:
|
||||
return v, nil
|
||||
case int:
|
||||
return uint(v), nil
|
||||
case float64:
|
||||
return uint(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected type for inbox_id: %T", raw)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractAssigneeID extracts an assignee (agent) ID from event data.
|
||||
func ExtractAssigneeID(data map[string]interface{}) (uint, error) {
|
||||
raw, ok := data["assignee_id"]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("assignee_id not found in event data")
|
||||
}
|
||||
switch v := raw.(type) {
|
||||
case uint:
|
||||
return v, nil
|
||||
case int:
|
||||
return uint(v), nil
|
||||
case float64:
|
||||
return uint(v), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected type for assignee_id: %T", raw)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user