chore: import clean Agent Desk snapshot (3/7)
This commit is contained in:
307
internal/services/system_config_service.go
Normal file
307
internal/services/system_config_service.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/dto"
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/pkg/errorsx"
|
||||
"agent-desk/internal/pkg/i18nx"
|
||||
"agent-desk/internal/pkg/utils"
|
||||
"agent-desk/internal/repositories"
|
||||
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
)
|
||||
|
||||
var SystemConfigService = newSystemConfigService()
|
||||
|
||||
func newSystemConfigService() *systemConfigService {
|
||||
return &systemConfigService{}
|
||||
}
|
||||
|
||||
type systemConfigService struct {
|
||||
}
|
||||
|
||||
const (
|
||||
systemConfigGroupSupportCenter = "support"
|
||||
systemConfigKeySupportNavMenu = "navigationMenu"
|
||||
|
||||
// The branding group is what lets an operator rename a deployment without
|
||||
// editing configuration and restarting. It is deliberately separate from the
|
||||
// support group: the name is read on every page load by both the visitor
|
||||
// portal and the dashboard, while the navigation menu is a portal concern.
|
||||
systemConfigGroupBranding = "branding"
|
||||
systemConfigKeyBrandingCompanyName = "companyName"
|
||||
systemConfigKeyBrandingCompanyLogo = "companyLogoUrl"
|
||||
)
|
||||
|
||||
type configValidator interface {
|
||||
Validate(raw json.RawMessage) (json.RawMessage, []response.ConfigFieldError, error)
|
||||
}
|
||||
|
||||
type systemConfigDefinition struct {
|
||||
GroupCode string
|
||||
Key string
|
||||
TitleKey string
|
||||
DescriptionKey string
|
||||
DefaultValue any
|
||||
Validator configValidator
|
||||
}
|
||||
|
||||
type SystemConfigValidationError struct {
|
||||
errors []response.ConfigFieldError
|
||||
}
|
||||
|
||||
func (e *SystemConfigValidationError) Error() string {
|
||||
return e.Message(i18nx.DefaultLocale)
|
||||
}
|
||||
|
||||
func (e *SystemConfigValidationError) Message(locale string) string {
|
||||
if len(e.errors) == 0 {
|
||||
return i18nx.Getf(locale, "error.supportConfig.validationFailed")
|
||||
}
|
||||
fieldErrorMessage := e.errors[0].Message
|
||||
if e.errors[0].MessageKey != "" {
|
||||
fieldErrorMessage = i18nx.Getf(locale, e.errors[0].MessageKey)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", i18nx.Getf(locale, "error.supportConfig.validationFailed"), fieldErrorMessage)
|
||||
}
|
||||
|
||||
func (e *SystemConfigValidationError) FieldErrors() []response.ConfigFieldError {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.errors
|
||||
}
|
||||
|
||||
func (e *SystemConfigValidationError) FieldErrorsLocale(locale string) []response.ConfigFieldError {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return localizeConfigFieldErrors(e.errors, locale)
|
||||
}
|
||||
|
||||
var systemConfigDefinitions = map[string]map[string]systemConfigDefinition{
|
||||
systemConfigGroupSupportCenter: {
|
||||
systemConfigKeySupportNavMenu: {
|
||||
GroupCode: systemConfigGroupSupportCenter,
|
||||
Key: systemConfigKeySupportNavMenu,
|
||||
TitleKey: "systemConfig.support.navigationMenu.title",
|
||||
DescriptionKey: "systemConfig.support.navigationMenu.description",
|
||||
DefaultValue: defaultSupportNavigationMenu(),
|
||||
Validator: supportNavigationMenuValidator{},
|
||||
},
|
||||
},
|
||||
systemConfigGroupBranding: {
|
||||
systemConfigKeyBrandingCompanyName: {
|
||||
GroupCode: systemConfigGroupBranding,
|
||||
Key: systemConfigKeyBrandingCompanyName,
|
||||
TitleKey: "systemConfig.branding.companyName.title",
|
||||
DescriptionKey: "systemConfig.branding.companyName.description",
|
||||
DefaultValue: "",
|
||||
Validator: brandingCompanyNameValidator{},
|
||||
},
|
||||
systemConfigKeyBrandingCompanyLogo: {
|
||||
GroupCode: systemConfigGroupBranding,
|
||||
Key: systemConfigKeyBrandingCompanyLogo,
|
||||
TitleKey: "systemConfig.branding.companyLogoUrl.title",
|
||||
DescriptionKey: "systemConfig.branding.companyLogoUrl.description",
|
||||
DefaultValue: "",
|
||||
Validator: brandingCompanyLogoURLValidator{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func (s *systemConfigService) Get(id int64) *models.SystemConfig {
|
||||
return repositories.SystemConfigRepository.Get(sqls.DB(), id)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) Find(cnd *sqls.Cnd) []models.SystemConfig {
|
||||
return repositories.SystemConfigRepository.Find(sqls.DB(), cnd)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) FindByGroupCode(groupCode string) []models.SystemConfig {
|
||||
return repositories.SystemConfigRepository.FindByGroupCode(sqls.DB(), groupCode)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) GetByGroupAndKey(groupCode, key string) *models.SystemConfig {
|
||||
return repositories.SystemConfigRepository.FindByGroupAndKey(sqls.DB(), groupCode, key)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) FindOne(cnd *sqls.Cnd) *models.SystemConfig {
|
||||
return repositories.SystemConfigRepository.FindOne(sqls.DB(), cnd)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) FindPageByCnd(cnd *sqls.Cnd) (list []models.SystemConfig, paging *sqls.Paging) {
|
||||
return repositories.SystemConfigRepository.FindPageByCnd(sqls.DB(), cnd)
|
||||
}
|
||||
|
||||
func (s *systemConfigService) GetPublicSupportConfig() response.PublicSupportConfigResponse {
|
||||
return response.PublicSupportConfigResponse{
|
||||
NavigationMenu: s.enabledSupportNavigationMenu(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *systemConfigService) GetDashboardSupportConfig() response.DashboardSupportConfigResponse {
|
||||
return response.DashboardSupportConfigResponse{
|
||||
NavigationMenu: s.supportNavigationMenu(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *systemConfigService) SaveSupportConfig(payload map[string]json.RawMessage, operator *dto.AuthPrincipal) (response.DashboardSupportConfigResponse, error) {
|
||||
if err := s.SaveGroupConfig(systemConfigGroupSupportCenter, payload, operator); err != nil {
|
||||
return response.DashboardSupportConfigResponse{}, err
|
||||
}
|
||||
return s.GetDashboardSupportConfig(), nil
|
||||
}
|
||||
|
||||
func (s *systemConfigService) SaveGroupConfig(groupCode string, payload map[string]json.RawMessage, operator *dto.AuthPrincipal) error {
|
||||
definitions := systemConfigDefinitions[groupCode]
|
||||
if len(definitions) == 0 {
|
||||
return errorsx.InvalidParamI18n("error.supportConfig.groupUnsupported")
|
||||
}
|
||||
if len(payload) == 0 {
|
||||
return errorsx.InvalidParamI18n("error.supportConfig.emptyPayload")
|
||||
}
|
||||
|
||||
values := make(map[string]json.RawMessage, len(payload))
|
||||
for key, raw := range payload {
|
||||
definition, ok := definitions[key]
|
||||
if !ok {
|
||||
return errorsx.InvalidParamI18n("error.supportConfig.keyUnsupported", key)
|
||||
}
|
||||
normalized := raw
|
||||
if definition.Validator != nil {
|
||||
next, fieldErrors, err := definition.Validator.Validate(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(fieldErrors) > 0 {
|
||||
return &SystemConfigValidationError{errors: fieldErrors}
|
||||
}
|
||||
normalized = next
|
||||
}
|
||||
values[key] = normalized
|
||||
}
|
||||
|
||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||
now := time.Now()
|
||||
auditFields := utils.BuildAuditFields(operator)
|
||||
for key, raw := range values {
|
||||
definition := definitions[key]
|
||||
existing := repositories.SystemConfigRepository.FindByGroupAndKey(ctx.Tx, groupCode, key)
|
||||
if existing == nil {
|
||||
item := &models.SystemConfig{
|
||||
ConfigKey: key,
|
||||
ConfigValue: string(raw),
|
||||
GroupCode: groupCode,
|
||||
Title: definition.Title(),
|
||||
Description: definition.Description(),
|
||||
Status: enums.StatusOk,
|
||||
AuditFields: auditFields,
|
||||
}
|
||||
if err := repositories.SystemConfigRepository.Create(ctx.Tx, item); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
columns := map[string]any{
|
||||
"config_value": string(raw),
|
||||
"group_code": groupCode,
|
||||
"title": definition.Title(),
|
||||
"description": definition.Description(),
|
||||
"status": enums.StatusOk,
|
||||
"updated_at": now,
|
||||
"update_user_id": auditFields.UpdateUserID,
|
||||
"update_user_name": auditFields.UpdateUserName,
|
||||
}
|
||||
if err := repositories.SystemConfigRepository.Updates(ctx.Tx, existing.ID, columns); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *systemConfigService) UpdateSupportNavigationMenu(items []request.SupportNavigationMenuItemRequest, operator *dto.AuthPrincipal) ([]response.SupportNavigationMenuItemResponse, error) {
|
||||
raw, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = s.SaveSupportConfig(map[string]json.RawMessage{
|
||||
systemConfigKeySupportNavMenu: raw,
|
||||
}, operator)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.supportNavigationMenu(), nil
|
||||
}
|
||||
|
||||
func (s *systemConfigService) enabledSupportNavigationMenu() []response.SupportNavigationMenuItemResponse {
|
||||
items := s.supportNavigationMenu()
|
||||
enabled := make([]response.SupportNavigationMenuItemResponse, 0, len(items))
|
||||
for _, item := range items {
|
||||
if item.Visible {
|
||||
item.Children = visibleSupportNavigationChildren(item.Children)
|
||||
enabled = append(enabled, item)
|
||||
}
|
||||
}
|
||||
if len(enabled) == 0 {
|
||||
return defaultSupportNavigationMenu()
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
func (s *systemConfigService) supportNavigationMenu() []response.SupportNavigationMenuItemResponse {
|
||||
item := repositories.SystemConfigRepository.FindByGroupAndKey(sqls.DB(), systemConfigGroupSupportCenter, systemConfigKeySupportNavMenu)
|
||||
if item == nil || strings.TrimSpace(item.ConfigValue) == "" {
|
||||
return defaultSupportNavigationMenu()
|
||||
}
|
||||
var list []response.SupportNavigationMenuItemResponse
|
||||
if err := json.Unmarshal([]byte(item.ConfigValue), &list); err != nil {
|
||||
return defaultSupportNavigationMenu()
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return defaultSupportNavigationMenu()
|
||||
}
|
||||
return sortSupportNavigationMenu(list)
|
||||
}
|
||||
|
||||
func sortSupportNavigationMenu(items []response.SupportNavigationMenuItemResponse) []response.SupportNavigationMenuItemResponse {
|
||||
ret := append([]response.SupportNavigationMenuItemResponse(nil), items...)
|
||||
for i := 0; i < len(ret)-1; i++ {
|
||||
for j := i + 1; j < len(ret); j++ {
|
||||
if ret[j].SortNo < ret[i].SortNo || (ret[j].SortNo == ret[i].SortNo && ret[j].ID < ret[i].ID) {
|
||||
ret[i], ret[j] = ret[j], ret[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func visibleSupportNavigationChildren(items []response.SupportNavigationMenuItemResponse) []response.SupportNavigationMenuItemResponse {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
visible := make([]response.SupportNavigationMenuItemResponse, 0, len(items))
|
||||
for _, item := range sortSupportNavigationMenu(items) {
|
||||
if item.Visible {
|
||||
visible = append(visible, item)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func (d systemConfigDefinition) Title() string {
|
||||
return i18nx.Get(d.TitleKey)
|
||||
}
|
||||
|
||||
func (d systemConfigDefinition) Description() string {
|
||||
return i18nx.Get(d.DescriptionKey)
|
||||
}
|
||||
Reference in New Issue
Block a user