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,234 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type chatwootContactMeta struct {
|
||||
Count int64 `json:"count"`
|
||||
CurrentPage int `json:"current_page"`
|
||||
HasMore *bool `json:"has_more,omitempty"`
|
||||
}
|
||||
|
||||
type chatwootCompanyMeta struct {
|
||||
TotalCount int64 `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
}
|
||||
|
||||
func contactListResponse(ctx context.Context, db *gorm.DB, contacts []model.Contact, total int64, page int, includeInboxes bool, hasMore *bool) map[string]any {
|
||||
payload := make([]any, 0, len(contacts))
|
||||
for i := range contacts {
|
||||
payload = append(payload, serializeCRMContact(ctx, db, &contacts[i], includeInboxes))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootContactMeta{Count: total, CurrentPage: page, HasMore: hasMore},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func contactPayloadResponse(ctx context.Context, db *gorm.DB, contact *model.Contact, includeInboxes bool) map[string]any {
|
||||
payload := serializeCRMContact(ctx, db, contact, includeInboxes)
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func contactCreateResponse(ctx context.Context, db *gorm.DB, contact *model.Contact, contactInbox *model.ContactInbox) map[string]any {
|
||||
payload := map[string]any{
|
||||
"contact": serializeCRMContact(ctx, db, contact, true),
|
||||
"contact_inbox": serializeContactInboxShell(ctx, db, contactInbox),
|
||||
}
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func serializeCRMContact(ctx context.Context, db *gorm.DB, contact *model.Contact, includeInboxes bool) map[string]any {
|
||||
payload := map[string]any{
|
||||
"additional_attributes": jsonObject(contact.AdditionalAttributes),
|
||||
"availability_status": contactAvailabilityStatus(ctx, contact),
|
||||
"email": contact.Email,
|
||||
"id": contact.ID,
|
||||
"name": contact.Name,
|
||||
"phone_number": contact.PhoneNumber,
|
||||
"blocked": contact.Blocked,
|
||||
"identifier": contact.Identifier,
|
||||
"thumbnail": contact.AvatarURL,
|
||||
"custom_attributes": jsonObject(contact.CustomAttributes),
|
||||
}
|
||||
if contact.LastActivityAt != nil {
|
||||
payload["last_activity_at"] = *contact.LastActivityAt
|
||||
}
|
||||
if !contact.CreatedAt.IsZero() {
|
||||
payload["created_at"] = contact.CreatedAt.Unix()
|
||||
}
|
||||
if includeInboxes {
|
||||
payload["contact_inboxes"] = []any{}
|
||||
if db != nil {
|
||||
var contactInboxes []model.ContactInbox
|
||||
if err := db.WithContext(ctx).Preload("Inbox").Where("contact_id = ?", contact.ID).Order("id ASC").Find(&contactInboxes).Error; err == nil {
|
||||
items := make([]any, 0, len(contactInboxes))
|
||||
for i := range contactInboxes {
|
||||
items = append(items, serializeContactInbox(&contactInboxes[i]))
|
||||
}
|
||||
payload["contact_inboxes"] = items
|
||||
}
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeCompanyContact(ctx context.Context, db *gorm.DB, contact *model.Contact, currentCompanyID uint) map[string]any {
|
||||
payload := serializeCRMContact(ctx, db, contact, false)
|
||||
payload["company_id"] = contact.CompanyID
|
||||
payload["linked_to_current_company"] = contact.CompanyID != nil && *contact.CompanyID == currentCompanyID
|
||||
payload["company"] = nil
|
||||
if db != nil && contact.CompanyID != nil {
|
||||
var company model.Company
|
||||
if err := db.WithContext(ctx).First(&company, *contact.CompanyID).Error; err == nil {
|
||||
payload["company"] = serializeCompany(ctx, db, &company)
|
||||
}
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeContactInbox(contactInbox *model.ContactInbox) map[string]any {
|
||||
return map[string]any{
|
||||
"source_id": contactInbox.SourceID,
|
||||
"inbox": serializeInboxSlim(&contactInbox.Inbox),
|
||||
}
|
||||
}
|
||||
|
||||
func serializeContactInboxShell(ctx context.Context, db *gorm.DB, contactInbox *model.ContactInbox) map[string]any {
|
||||
if contactInbox == nil {
|
||||
return map[string]any{"inbox": nil, "source_id": nil}
|
||||
}
|
||||
if contactInbox.Inbox.ID == 0 && db != nil && contactInbox.InboxID != 0 {
|
||||
_ = db.WithContext(ctx).First(&contactInbox.Inbox, contactInbox.InboxID).Error
|
||||
}
|
||||
return map[string]any{"inbox": serializeInboxSlim(&contactInbox.Inbox), "source_id": contactInbox.SourceID}
|
||||
}
|
||||
|
||||
func serializeInboxSlim(inbox *model.Inbox) map[string]any {
|
||||
if inbox == nil || inbox.ID == 0 {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"id": inbox.ID,
|
||||
"avatar_url": inbox.AvatarURL,
|
||||
"channel_id": inbox.ChannelID,
|
||||
"name": inbox.Name,
|
||||
"channel_type": inbox.ChannelType,
|
||||
"provider": configValue(parseInboxChannelConfig(inbox.ChannelConfig), "provider"),
|
||||
}
|
||||
}
|
||||
|
||||
func companyListResponse(ctx context.Context, db *gorm.DB, companies []model.Company, total int64, page int) map[string]any {
|
||||
payload := make([]any, 0, len(companies))
|
||||
for i := range companies {
|
||||
payload = append(payload, serializeCompany(ctx, db, &companies[i]))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootCompanyMeta{TotalCount: total, Page: page},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func companyPayloadResponse(ctx context.Context, db *gorm.DB, company *model.Company) map[string]any {
|
||||
payload := serializeCompany(ctx, db, company)
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func serializeCompany(ctx context.Context, db *gorm.DB, company *model.Company) map[string]any {
|
||||
return map[string]any{
|
||||
"id": company.ID,
|
||||
"name": company.Name,
|
||||
"contacts_count": companyContactsCount(ctx, db, company),
|
||||
"domain": company.Domain,
|
||||
"description": company.Description,
|
||||
"additional_attributes": jsonObject(company.AdditionalAttributes),
|
||||
"custom_attributes": jsonObject(company.CustomAttributes),
|
||||
"avatar_url": company.FaviconURL,
|
||||
"last_activity_at": unixTimePtr(company.LastActivityAt),
|
||||
"created_at": company.CreatedAt.Unix(),
|
||||
"updated_at": company.UpdatedAt.Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
func companyContactsResponse(ctx context.Context, db *gorm.DB, companyID uint, contacts []model.Contact, total int64, page int) map[string]any {
|
||||
payload := make([]any, 0, len(contacts))
|
||||
for i := range contacts {
|
||||
payload = append(payload, serializeCompanyContact(ctx, db, &contacts[i], companyID))
|
||||
}
|
||||
return map[string]any{
|
||||
"meta": chatwootCompanyMeta{TotalCount: total, Page: page},
|
||||
"payload": payload,
|
||||
}
|
||||
}
|
||||
|
||||
func companyNotesResponse(ctx context.Context, db *gorm.DB, notes []model.CompanyNote, accountID uint) map[string]any {
|
||||
payload := make([]any, 0, len(notes))
|
||||
for i := range notes {
|
||||
payload = append(payload, serializeCompanyNote(ctx, db, ¬es[i], accountID))
|
||||
}
|
||||
return map[string]any{"payload": payload}
|
||||
}
|
||||
|
||||
func contactNotesResponse(notes []model.Note) []any {
|
||||
payload := make([]any, 0, len(notes))
|
||||
for i := range notes {
|
||||
payload = append(payload, serializeContactNote(¬es[i]))
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
func serializeContactNote(note *model.Note) map[string]any {
|
||||
item := map[string]any{
|
||||
"id": note.ID,
|
||||
"content": note.Content,
|
||||
"account_id": note.AccountID,
|
||||
"contact_id": note.ContactID,
|
||||
"created_at": note.CreatedAt.Unix(),
|
||||
"updated_at": note.UpdatedAt.Unix(),
|
||||
}
|
||||
if note.UserID != nil {
|
||||
item["user_id"] = *note.UserID
|
||||
}
|
||||
if note.User != nil && note.User.ID != 0 {
|
||||
item["user"] = serializeUser(note.User, note.AccountID)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func serializeCompanyNote(ctx context.Context, db *gorm.DB, note *model.CompanyNote, accountID uint) map[string]any {
|
||||
item := map[string]any{
|
||||
"id": note.ID,
|
||||
"content": note.Content,
|
||||
"company_id": note.CompanyID,
|
||||
"user_id": note.UserID,
|
||||
"created_at": note.CreatedAt.Unix(),
|
||||
"updated_at": note.UpdatedAt.Unix(),
|
||||
}
|
||||
if note.User.ID != 0 {
|
||||
item["user"] = serializeUser(¬e.User, accountID)
|
||||
} else if db != nil && note.UserID != 0 {
|
||||
item["user"] = serializeUserFromDB(ctx, db, note.UserID, accountID)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func companyContactsCount(ctx context.Context, db *gorm.DB, company *model.Company) int64 {
|
||||
if db == nil || company == nil || company.ID == 0 {
|
||||
return 0
|
||||
}
|
||||
var count int64
|
||||
_ = db.WithContext(ctx).Model(&model.Contact{}).Where("account_id = ? AND company_id = ?", company.AccountID, company.ID).Count(&count).Error
|
||||
return count
|
||||
}
|
||||
|
||||
func unixTimePtr(value *time.Time) any {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return value.Unix()
|
||||
}
|
||||
Reference in New Issue
Block a user