Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package feeds
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// requiredStandardField is an enabled, required company standard field used for sync preflight.
|
||||
type requiredStandardField struct {
|
||||
Key string
|
||||
Name string
|
||||
}
|
||||
|
||||
// activeMappings returns rows with a non-empty source and a real target (not "none").
|
||||
// Mirrors the frontend activeMappingRows filter.
|
||||
func activeMappings(mappings []FieldMapping) []FieldMapping {
|
||||
out := make([]FieldMapping, 0, len(mappings))
|
||||
for _, m := range mappings {
|
||||
src := m.sourceKey()
|
||||
tgt := m.targetKey()
|
||||
if src == "" || tgt == "" || strings.EqualFold(tgt, "none") {
|
||||
continue
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// validateMappingsForSync returns a ClientMsg when mappings are empty or required targets are missing.
|
||||
// Optional (non-required) standard fields may remain unmapped.
|
||||
func validateMappingsForSync(mappings []FieldMapping, required []requiredStandardField) error {
|
||||
active := activeMappings(mappings)
|
||||
if len(active) == 0 {
|
||||
return ClientMsg("Map at least one source field before syncing.")
|
||||
}
|
||||
if len(required) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mapped := make(map[string]struct{}, len(active))
|
||||
for _, m := range active {
|
||||
mapped[m.targetKey()] = struct{}{}
|
||||
}
|
||||
|
||||
missing := make([]string, 0)
|
||||
for _, r := range required {
|
||||
if _, ok := mapped[r.Key]; ok {
|
||||
continue
|
||||
}
|
||||
label := strings.TrimSpace(r.Name)
|
||||
if label == "" {
|
||||
label = r.Key
|
||||
}
|
||||
missing = append(missing, label)
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ClientMsg(fmt.Sprintf("Map required fields before syncing: %s.", strings.Join(missing, ", ")))
|
||||
}
|
||||
|
||||
// loadRequiredStandardFields returns enabled+required standard field keys for the company.
|
||||
func (s *Service) loadRequiredStandardFields(ctx context.Context, companyID uuid.UUID) ([]requiredStandardField, error) {
|
||||
if s == nil || s.Pool == nil {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := s.Pool.Query(ctx, `
|
||||
SELECT key, COALESCE(NULLIF(TRIM(name), ''), key) AS name
|
||||
FROM standard_fields
|
||||
WHERE company_id = $1 AND enabled = true AND is_required = true
|
||||
ORDER BY sort_order ASC, key ASC`, companyID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]requiredStandardField, 0)
|
||||
for rows.Next() {
|
||||
var key, name string
|
||||
if err := rows.Scan(&key, &name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, requiredStandardField{Key: key, Name: strings.TrimSpace(name)})
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// ensureMappingsReadyForSync loads active mappings and required standard fields, then validates.
|
||||
// Called before creating a sync job so clients get a clear 400 without a failed job row.
|
||||
func (s *Service) ensureMappingsReadyForSync(ctx context.Context, companyID, feedID uuid.UUID) error {
|
||||
raw, err := s.loadMappingsRaw(ctx, companyID, feedID)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ClientMsg("Map at least one source field before syncing.")
|
||||
}
|
||||
return err
|
||||
}
|
||||
required, err := s.loadRequiredStandardFields(ctx, companyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return validateMappingsForSync(parseMappings(raw), required)
|
||||
}
|
||||
Reference in New Issue
Block a user