Align GoChat with Chatwoot frontend contracts

This commit is contained in:
2026-06-13 22:13:32 +08:00
parent 71abf58636
commit d884fdda0a
162 changed files with 9825 additions and 528 deletions
+49 -3
View File
@@ -3,6 +3,7 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"strings"
@@ -69,6 +70,7 @@ func seed() error {
if err != nil {
return fmt.Errorf("config load failed: %w", err)
}
cfg.Log.Level = "silent"
if err := applogger.Init(applogger.Config{Level: cfg.Log.Level, Format: cfg.Log.Format, Output: "stdout", ErrorOutput: "stderr"}); err != nil {
return fmt.Errorf("logger init failed: %w", err)
}
@@ -113,6 +115,8 @@ type smokeSeedSummary struct {
InboxID uint `json:"inbox_id"`
ContactID uint `json:"contact_id"`
CompanyID uint `json:"company_id"`
PortalID uint `json:"portal_id"`
ArticleID uint `json:"article_id"`
ConversationID uint `json:"conversation_id"`
ConversationDisplayID uint `json:"conversation_display_id"`
ConversationUID string `json:"conversation_uuid"`
@@ -145,10 +149,10 @@ func seedSmokeData(ctx context.Context, db *gorm.DB) (*smokeSeedSummary, error)
}
now := time.Now()
admin := &model.User{}
if err := db.WithContext(ctx).Where("email = ?", adminEmail).FirstOrCreate(admin, model.User{AccountID: account.ID, Name: adminName, DisplayName: adminName, Email: adminEmail, Password: hashed, PasswordDigest: hashed, Provider: "email", Role: "super_admin", Type: "User", Active: true, Available: true, ConfirmedAt: &now, UISettings: datatypes.JSON([]byte(`{}`)), CustomAttributes: datatypes.JSON([]byte(`{}`))}).Error; err != nil {
if err := firstOrCreateBy(ctx, db, admin, model.User{Email: adminEmail}, model.User{AccountID: account.ID, Name: adminName, DisplayName: adminName, Email: adminEmail, Password: hashed, PasswordDigest: hashed, Provider: "email", Role: "super_admin", Type: "User", Active: true, Available: true, ConfirmedAt: &now, UISettings: datatypes.JSON([]byte(`{}`)), CustomAttributes: datatypes.JSON([]byte(`{}`))}); err != nil {
return nil, fmt.Errorf("seed admin user: %w", err)
}
if err := db.WithContext(ctx).Model(admin).Updates(map[string]any{"account_id": account.ID, "password": hashed, "password_digest": hashed, "active": true, "available": true}).Error; err != nil {
if err := db.WithContext(ctx).Model(admin).Updates(map[string]any{"account_id": account.ID, "name": adminName, "display_name": adminName, "password": hashed, "password_digest": hashed, "provider": "email", "role": "super_admin", "type": "User", "active": true, "available": true, "confirmed_at": now}).Error; err != nil {
return nil, fmt.Errorf("update admin user: %w", err)
}
@@ -173,6 +177,32 @@ func seedSmokeData(ctx context.Context, db *gorm.DB) (*smokeSeedSummary, error)
if err := db.WithContext(ctx).Where("account_id = ? AND name = ?", account.ID, "Smoke Company").FirstOrCreate(company, model.Company{AccountID: account.ID, Name: "Smoke Company", Domain: "gochat.local", WebsiteURL: "https://gochat.local", CustomAttributes: datatypes.JSON([]byte(`{"tier":"enterprise"}`))}).Error; err != nil {
return nil, fmt.Errorf("seed company: %w", err)
}
if err := db.WithContext(ctx).Model(company).Updates(map[string]any{"domain": "gochat.local", "website_url": "https://gochat.local", "custom_attributes": datatypes.JSON([]byte(`{"tier":"enterprise"}`))}).Error; err != nil {
return nil, fmt.Errorf("update smoke company: %w", err)
}
portalSlug := fmt.Sprintf("gochat-smoke-portal-%d", account.ID)
portal := &model.Portal{}
if err := db.WithContext(ctx).Where("account_id = ? AND slug = ?", account.ID, portalSlug).FirstOrCreate(portal, model.Portal{AccountID: account.ID, Name: "Smoke Help Center", Slug: portalSlug, Description: "B12 frontend smoke help center", HeaderText: "How can we help?", PageTitle: "Smoke Help Center", Color: "#1f93ff", Locale: "en", PortalConfiguration: json.RawMessage(`{"allowed_locales":["en"],"default_locale":"en"}`)}).Error; err != nil {
return nil, fmt.Errorf("seed portal: %w", err)
}
if err := db.WithContext(ctx).Model(portal).Updates(map[string]any{"name": "Smoke Help Center", "description": "B12 frontend smoke help center", "locale": "en"}).Error; err != nil {
return nil, fmt.Errorf("update smoke portal: %w", err)
}
category := &model.Category{}
if err := db.WithContext(ctx).Where("account_id = ? AND portal_id = ? AND slug = ?", account.ID, portal.ID, "smoke-guides").FirstOrCreate(category, model.Category{AccountID: account.ID, PortalID: portal.ID, Name: "Smoke Guides", Slug: "smoke-guides", Description: "B12 frontend smoke guides", Locale: "en", Position: 1, CustomAttributes: json.RawMessage(`{}`)}).Error; err != nil {
return nil, fmt.Errorf("seed category: %w", err)
}
articleSlug := fmt.Sprintf("gochat-smoke-onboarding-%d", account.ID)
article := &model.Article{}
if err := db.WithContext(ctx).Where("account_id = ? AND slug = ?", account.ID, articleSlug).FirstOrCreate(article, model.Article{AccountID: account.ID, PortalID: portal.ID, CategoryID: &category.ID, AuthorID: &admin.ID, Title: "Smoke Onboarding Guide", Slug: articleSlug, Description: "B12 frontend smoke article", Content: "Use this onboarding guide to verify Woochat search direct-connect behavior.", Status: "published", Position: 1, Locale: "en", Meta: json.RawMessage(`{}`), CustomAttributes: json.RawMessage(`{}`)}).Error; err != nil {
return nil, fmt.Errorf("seed article: %w", err)
}
if err := db.WithContext(ctx).Model(article).Updates(map[string]any{"portal_id": portal.ID, "category_id": category.ID, "author_id": admin.ID, "title": "Smoke Onboarding Guide", "description": "B12 frontend smoke article", "content": "Use this onboarding guide to verify Woochat search direct-connect behavior.", "status": "published", "locale": "en"}).Error; err != nil {
return nil, fmt.Errorf("update smoke article: %w", err)
}
contact := &model.Contact{}
if err := db.WithContext(ctx).Where("account_id = ? AND email = ?", account.ID, "customer@gochat.local").FirstOrCreate(contact, model.Contact{AccountID: account.ID, CompanyID: &company.ID, Name: "Smoke Customer", Email: "customer@gochat.local", Identifier: "gochat-smoke-customer", ContactType: "lead", AdditionalAttributes: datatypes.JSON([]byte(`{}`)), CustomAttributes: datatypes.JSON([]byte(`{"plan":"enterprise"}`))}).Error; err != nil {
@@ -238,7 +268,7 @@ func seedSmokeData(ctx context.Context, db *gorm.DB) (*smokeSeedSummary, error)
if conversation.DisplayID != nil {
conversationDisplayID = *conversation.DisplayID
}
return &smokeSeedSummary{AdminEmail: adminEmail, AdminPassword: adminPassword, AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, CompanyID: company.ID, ConversationID: conversation.ID, ConversationDisplayID: conversationDisplayID, ConversationUID: conversation.UUID, CsatMessageID: csatMessage.ID, SlaPolicyID: sla.ID, CustomRoleID: customRole.ID, CapacityPolicyID: capacity.ID, CaptainAssistantID: assistant.ID}, nil
return &smokeSeedSummary{AdminEmail: adminEmail, AdminPassword: adminPassword, AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, CompanyID: company.ID, PortalID: portal.ID, ArticleID: article.ID, ConversationID: conversation.ID, ConversationDisplayID: conversationDisplayID, ConversationUID: conversation.UUID, CsatMessageID: csatMessage.ID, SlaPolicyID: sla.ID, CustomRoleID: customRole.ID, CapacityPolicyID: capacity.ID, CaptainAssistantID: assistant.ID}, nil
}
func seedMessage(ctx context.Context, db *gorm.DB, conversation *model.Conversation, inboxID, senderID uint, messageType, contentType, content string) (*model.Message, error) {
@@ -264,6 +294,22 @@ func seedMessage(ctx context.Context, db *gorm.DB, conversation *model.Conversat
return message, nil
}
func firstOrCreateBy[T any](ctx context.Context, db *gorm.DB, dest *T, query T, attrs T) error {
result := db.WithContext(ctx).Where(query).First(dest)
if result.Error == nil {
return nil
}
if !errors.Is(result.Error, gorm.ErrRecordNotFound) {
return result.Error
}
record := attrs
if err := db.WithContext(ctx).Create(&record).Error; err != nil {
return err
}
*dest = record
return nil
}
func getenvDefault(key, fallback string) string {
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
return value
-2
View File
@@ -204,8 +204,6 @@ func reindexContacts(ctx context.Context, engine search.SearchEngine, db *gorm.D
for {
var rows []model.Contact
q := db.WithContext(ctx).
Preload("Portal").
Preload("Category").
Where("id > ?", lastID).Order("id ASC").Limit(batchSize)
if accountID != 0 {
q = q.Where("account_id = ?", accountID)