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,534 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
applogger "github.com/gochat/gochat/pkg/logger"
|
||||
"github.com/gochat/gochat/pkg/pagination"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
|
||||
// AccountHandler handles account-related API endpoints.
|
||||
// Reference: Chatwoot app/controllers/api/v1/accounts_controller.rb
|
||||
type AccountHandler struct {
|
||||
svc *service.AccountService
|
||||
}
|
||||
|
||||
// NewAccountHandler creates a new AccountHandler.
|
||||
func NewAccountHandler(svc *service.AccountService) *AccountHandler {
|
||||
return &AccountHandler{svc: svc}
|
||||
}
|
||||
|
||||
// @Summary List accounts accessible by the current user
|
||||
// @Description Returns all accounts the authenticated user has access to, with pagination support
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number" default(1)
|
||||
// @Param page_size query int false "Items per page" default(25)
|
||||
// @Success 200 {object} []model.Account
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts [get]
|
||||
func (h *AccountHandler) List(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
if userID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
accounts, total, err := h.svc.ListByUser(c.Request.Context(), userID, pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("List accounts: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list accounts")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, accounts, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// @Summary Get a single account by ID
|
||||
// @Description Retrieves detailed information about a specific account
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 404 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [get]
|
||||
func (h *AccountHandler) Get(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.GetByID(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Get account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusNotFound, response.ErrAccountNotFound, "account not found")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, serializeAccount(account))
|
||||
}
|
||||
|
||||
// @Summary Create a new account
|
||||
// @Description Creates a new account and assigns the creator as administrator
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param body body service.CreateAccountRequest true "Account creation payload"
|
||||
// @Success 201 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts [post]
|
||||
func (h *AccountHandler) Create(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
if userID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.CreateAccountRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.Create(c.Request.Context(), userID, req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Create account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create account")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": gin.H{"id": userID, "account_id": account.ID}})
|
||||
}
|
||||
|
||||
// @Summary Update an existing account
|
||||
// @Description Modifies account details such as name and settings
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Param body body service.UpdateAccountRequest true "Account update payload"
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [put]
|
||||
func (h *AccountHandler) Update(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateAccountRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.Update(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Update account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update account")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, serializeAccount(account))
|
||||
}
|
||||
|
||||
// UpdateOnboarding updates account details from the dashboard onboarding flow.
|
||||
// PATCH /api/v1/accounts/:account_id/onboarding
|
||||
// Reference: Chatwoot Api::V1::Accounts::OnboardingsController#update.
|
||||
func (h *AccountHandler) UpdateOnboarding(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateAccountOnboardingRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.UpdateOnboarding(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Update onboarding account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update account onboarding")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, serializeAccount(account))
|
||||
}
|
||||
|
||||
// @Summary Delete an account
|
||||
// @Description Soft-deletes an account by ID
|
||||
// @Tags Accounts
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path uint true "Account ID"
|
||||
// @Success 204 "No Content"
|
||||
// @Failure 400 {object} model.ErrorResponse
|
||||
// @Failure 401 {object} model.ErrorResponse
|
||||
// @Failure 500 {object} model.ErrorResponse
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /api/v1/accounts/{id} [delete]
|
||||
func (h *AccountHandler) Delete(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.Delete(c.Request.Context(), id); err != nil {
|
||||
applogger.L().Errorf("Delete account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to delete account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// ListUsers returns all users belonging to an account.
|
||||
// GET /api/v1/accounts/:id/users
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#index
|
||||
func (h *AccountHandler) ListUsers(c *gin.Context) {
|
||||
accountID := parseAccountIDParam(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
users, total, err := h.svc.ListUsers(c.Request.Context(), accountID, pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("List account users: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list account users")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, users, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// AddUser adds a user to an account with a specified role.
|
||||
// POST /api/v1/accounts/:id/users
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#create
|
||||
func (h *AccountHandler) AddUser(c *gin.Context) {
|
||||
accountID := parseAccountIDParam(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.AddUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.AddUser(c.Request.Context(), accountID, req); err != nil {
|
||||
applogger.L().Errorf("Add user to account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to add user to account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// RemoveUser removes a user from an account.
|
||||
// DELETE /api/v1/accounts/:id/users/:user_id
|
||||
// Reference: Chatwoot Accounts::AccountUsersController#destroy
|
||||
func (h *AccountHandler) RemoveUser(c *gin.Context) {
|
||||
accountID := parseAccountIDParam(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := parseUintParam(c, "user_id")
|
||||
if err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user id")
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.svc.RemoveUser(c.Request.Context(), accountID, userID); err != nil {
|
||||
applogger.L().Errorf("Remove user from account: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to remove user from account")
|
||||
return
|
||||
}
|
||||
|
||||
response.NoContent(c)
|
||||
}
|
||||
|
||||
// GetAll lists all accounts (platform admin level, no user-scoping).
|
||||
// GET /api/v1/accounts/all
|
||||
// Reference: Chatwoot platform admin listing all accounts
|
||||
func (h *AccountHandler) GetAll(c *gin.Context) {
|
||||
pg := pagination.Parse(c)
|
||||
accounts, total, err := h.svc.GetAll(c.Request.Context(), pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("GetAll accounts: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list all accounts")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, accounts, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// GetAgents retrieves all agents (AccountUser records) for an account.
|
||||
// GET /api/v1/accounts/:id/agents
|
||||
// Reference: Chatwoot app/controllers/api/v1/accounts/agents_controller.rb#index
|
||||
func (h *AccountHandler) GetAgents(c *gin.Context) {
|
||||
accountID := parseAccountIDParam(c)
|
||||
if accountID == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
pg := pagination.Parse(c)
|
||||
agents, total, err := h.svc.GetAgents(c.Request.Context(), accountID, pg.Offset, pg.PerPage)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("GetAgents for account %d: %v", accountID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list agents")
|
||||
return
|
||||
}
|
||||
|
||||
response.OKWithMeta(c, agents, pg.Page, pg.PerPage, total)
|
||||
}
|
||||
|
||||
// UpdateSettings updates account-level settings.
|
||||
// PUT /api/v1/accounts/:id/settings
|
||||
// Reference: Chatwoot AccountsController#update (settings subset)
|
||||
func (h *AccountHandler) UpdateSettings(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
var req service.UpdateAccountSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
account, err := h.svc.UpdateSettings(c.Request.Context(), id, req)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("Update account settings: %v", err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update account settings")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, serializeAccount(account))
|
||||
}
|
||||
|
||||
// --- Account extension handlers (G8) ---
|
||||
// Reference: Chatwoot accounts_controller.rb#update_active_at, #cache_keys
|
||||
|
||||
// UpdateActiveAt updates the active_at timestamp for the current user in an account.
|
||||
// POST /api/v1/accounts/:id/update_active_at
|
||||
func (h *AccountHandler) UpdateActiveAt(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
if err := h.svc.UpdateActiveAt(c.Request.Context(), id, userID); err != nil {
|
||||
applogger.L().Errorf("UpdateActiveAt for account %d, user %d: %v", id, userID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to update active_at")
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
// CacheKeys returns cache key identifiers for frontend cache invalidation.
|
||||
// GET /api/v1/accounts/:id/cache_keys
|
||||
func (h *AccountHandler) CacheKeys(c *gin.Context) {
|
||||
id := parseAccountIDParam(c)
|
||||
if id == 0 {
|
||||
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account id")
|
||||
return
|
||||
}
|
||||
|
||||
userID := getUserID(c)
|
||||
keys, err := h.svc.CacheKeys(c.Request.Context(), id, userID)
|
||||
if err != nil {
|
||||
applogger.L().Errorf("CacheKeys for account %d, user %d: %v", id, userID, err)
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to get cache keys")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"cache_keys": keys})
|
||||
}
|
||||
|
||||
func serializeAccount(account *model.Account) map[string]any {
|
||||
if account == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
customAttributes := serializeAccountCustomAttributes(account)
|
||||
return map[string]any{
|
||||
"settings": serializeAccountSettings(account),
|
||||
"created_at": account.CreatedAt,
|
||||
"domain": account.Domain,
|
||||
"features": parseAccountFeatures(account.FeatureFlags),
|
||||
"id": account.ID,
|
||||
"locale": nonEmpty(account.Locale, "en"),
|
||||
"name": account.Name,
|
||||
"support_email": nil,
|
||||
"status": nonEmpty(account.Status, "active"),
|
||||
"cache_keys": map[string]string{"label": "0000000000", "inbox": "0000000000", "team": "0000000000"},
|
||||
"custom_attributes": customAttributes,
|
||||
}
|
||||
}
|
||||
|
||||
func serializeAccountCustomAttributes(account *model.Account) map[string]any {
|
||||
attrs := account.CustomAttributesMap()
|
||||
out := map[string]any{
|
||||
"plan_name": attrs["plan_name"],
|
||||
"subscribed_quantity": attrs["subscribed_quantity"],
|
||||
"subscription_status": attrs["subscription_status"],
|
||||
"subscription_ends_on": attrs["subscription_ends_on"],
|
||||
}
|
||||
|
||||
copyPresentAttribute(out, attrs, "website")
|
||||
copyPresentAttribute(out, attrs, "industry")
|
||||
copyPresentAttribute(out, attrs, "company_size")
|
||||
copyPresentAttribute(out, attrs, "timezone")
|
||||
copyPresentAttribute(out, attrs, "logo")
|
||||
copyPresentAttribute(out, attrs, "referral_source")
|
||||
copyPresentAttribute(out, attrs, "brand_info")
|
||||
if account.OnboardingStep != "" {
|
||||
out["onboarding_step"] = account.OnboardingStep
|
||||
} else if isPresent(attrs["onboarding_step"]) {
|
||||
out["onboarding_step"] = attrs["onboarding_step"]
|
||||
}
|
||||
copyPresentAttribute(out, attrs, "marked_for_deletion_at")
|
||||
copyPresentAttribute(out, attrs, "marked_for_deletion_reason")
|
||||
if _, ok := out["timezone"]; !ok && account.Timezone != "" {
|
||||
out["timezone"] = account.Timezone
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func copyPresentAttribute(out map[string]any, attrs map[string]any, key string) {
|
||||
if value, ok := attrs[key]; ok && isPresent(value) {
|
||||
out[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func isPresent(value any) bool {
|
||||
if value == nil {
|
||||
return false
|
||||
}
|
||||
if s, ok := value.(string); ok {
|
||||
return s != ""
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func serializeAccountSettings(account *model.Account) map[string]any {
|
||||
settings := map[string]any{
|
||||
"auto_resolve_after": account.AutoResolveDuration,
|
||||
"auto_resolve_duration": account.AutoResolveDuration,
|
||||
"auto_resolve_message": "",
|
||||
"auto_resolve_ignore_waiting": false,
|
||||
"audio_transcriptions": false,
|
||||
"auto_resolve_label": "",
|
||||
"reporting_timezone": account.ReportingTimezone,
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
var chatwootDefaultEnabledAccountFeatures = []string{
|
||||
"inbound_emails",
|
||||
"channel_email",
|
||||
"channel_facebook",
|
||||
"help_center",
|
||||
"agent_bots",
|
||||
"macros",
|
||||
"agent_management",
|
||||
"team_management",
|
||||
"inbox_management",
|
||||
"labels",
|
||||
"custom_attributes",
|
||||
"automations",
|
||||
"canned_responses",
|
||||
"integrations",
|
||||
"voice_recorder",
|
||||
"channel_website",
|
||||
"campaigns",
|
||||
"reports",
|
||||
"crm",
|
||||
"auto_resolve_conversations",
|
||||
"chatwoot_v4",
|
||||
"contact_chatwoot_support_team",
|
||||
"channel_instagram",
|
||||
"channel_tiktok",
|
||||
"assignment_v2",
|
||||
"captain_tasks",
|
||||
}
|
||||
|
||||
func parseAccountFeatures(raw string) map[string]bool {
|
||||
features := defaultEnabledAccountFeatures()
|
||||
if raw == "" {
|
||||
return onlyEnabledAccountFeatures(features)
|
||||
}
|
||||
|
||||
objectFlags := map[string]bool{}
|
||||
if err := json.Unmarshal([]byte(raw), &objectFlags); err == nil {
|
||||
for key, enabled := range objectFlags {
|
||||
features[key] = enabled
|
||||
}
|
||||
return onlyEnabledAccountFeatures(features)
|
||||
}
|
||||
|
||||
arrayFlags := []string{}
|
||||
if err := json.Unmarshal([]byte(raw), &arrayFlags); err == nil {
|
||||
for _, key := range arrayFlags {
|
||||
features[key] = true
|
||||
}
|
||||
return onlyEnabledAccountFeatures(features)
|
||||
}
|
||||
|
||||
return onlyEnabledAccountFeatures(features)
|
||||
}
|
||||
|
||||
func defaultEnabledAccountFeatures() map[string]bool {
|
||||
features := make(map[string]bool, len(chatwootDefaultEnabledAccountFeatures))
|
||||
for _, key := range chatwootDefaultEnabledAccountFeatures {
|
||||
features[key] = true
|
||||
}
|
||||
return features
|
||||
}
|
||||
|
||||
func onlyEnabledAccountFeatures(features map[string]bool) map[string]bool {
|
||||
enabled := map[string]bool{}
|
||||
for key, value := range features {
|
||||
if value {
|
||||
enabled[key] = true
|
||||
}
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
Reference in New Issue
Block a user