Files
gochat/backend/internal/model/account_email.go
T
rogee aeddedf2a3 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.
2026-07-07 14:44:12 +08:00

134 lines
3.0 KiB
Go

package model
import (
"encoding/json"
"strconv"
"strings"
"time"
"gorm.io/datatypes"
)
const accountOutboundEmailCountKey = "_outbound_email_count"
// LimitsMap returns the Chatwoot-style account limits JSON object.
func (a *Account) LimitsMap() map[string]any {
return jsonMap(a.Limits)
}
// EmailTranscriptEnabled mirrors Account#email_transcript_enabled?. Community
// accounts allow transcripts by default; enterprise tests may disable it via
// limits or custom attributes.
func (a *Account) EmailTranscriptEnabled() bool {
if a == nil {
return true
}
for _, attrs := range []map[string]any{a.LimitsMap(), a.CustomAttributesMap()} {
if value, ok := firstPresent(attrs, "email_transcript_enabled", "email_transcript"); ok {
return truthy(value, true)
}
}
return true
}
// EmailRateLimit returns the configured daily transcript/outbound email limit.
// A zero value means unlimited, matching non-cloud Chatwoot behavior.
func (a *Account) EmailRateLimit() int {
if a == nil {
return 0
}
for _, attrs := range []map[string]any{a.LimitsMap(), a.CustomAttributesMap()} {
if value, ok := firstPresent(attrs, "emails", "email_rate_limit", "outbound_email_limit"); ok {
if n := intValue(value); n > 0 {
return n
}
}
}
return 0
}
func (a *Account) EmailsSentToday(now time.Time) int {
attrs := a.CustomAttributesMap()
value, ok := attrs[accountOutboundEmailCountKey]
if !ok {
return intValue(attrs["emails_sent_today"])
}
record, ok := value.(map[string]any)
if !ok || strings.TrimSpace(stringValue(record["date"])) != now.Format("2006-01-02") {
return 0
}
return intValue(record["count"])
}
func (a *Account) IncrementEmailSentCount(now time.Time) error {
attrs := a.CustomAttributesMap()
count := a.EmailsSentToday(now) + 1
attrs[accountOutboundEmailCountKey] = map[string]any{
"date": now.Format("2006-01-02"),
"count": count,
}
return a.SetCustomAttributesMap(attrs)
}
func jsonMap(raw datatypes.JSON) map[string]any {
attrs := map[string]any{}
if len(raw) > 0 {
_ = json.Unmarshal(raw, &attrs)
}
return attrs
}
func firstPresent(attrs map[string]any, keys ...string) (any, bool) {
for _, key := range keys {
if value, ok := attrs[key]; ok {
return value, true
}
}
return nil, false
}
func truthy(value any, defaultValue bool) bool {
switch v := value.(type) {
case bool:
return v
case string:
s := strings.TrimSpace(strings.ToLower(v))
if s == "" {
return defaultValue
}
return s == "true" || s == "1" || s == "yes" || s == "enabled"
case float64:
return v != 0
case int:
return v != 0
default:
return defaultValue
}
}
func intValue(value any) int {
switch v := value.(type) {
case int:
return v
case int64:
return int(v)
case float64:
return int(v)
case json.Number:
n, _ := v.Int64()
return int(n)
case string:
n, _ := strconv.Atoi(strings.TrimSpace(v))
return n
default:
return 0
}
}
func stringValue(value any) string {
if s, ok := value.(string); ok {
return s
}
return ""
}