Files
greeneclipse 8580c996c3 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.
2026-08-09 22:47:43 +02:00

174 lines
4.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
"github.com/google/uuid"
)
// IDMapDocument matches docs/schema-map.md (versioned unified ID map for cutover rehearsal).
type IDMapDocument struct {
Version int `json:"version"`
GeneratedAt string `json:"generated_at"`
Source string `json:"source"`
Target string `json:"target"`
Users map[string]string `json:"users"`
Companies map[string]string `json:"companies"`
Categories map[string]string `json:"categories,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"`
Feeds map[string]string `json:"feeds,omitempty"`
RawProducts map[string]string `json:"raw_products,omitempty"`
Files map[string]string `json:"files,omitempty"`
Meta IDMapMeta `json:"meta"`
}
type IDMapMeta struct {
UserCount int `json:"user_count"`
CompanyCount int `json:"company_count"`
DryRun bool `json:"dry_run"`
Report map[string]int `json:"report,omitempty"`
}
func copyMap(dst *map[string]string, src map[string]string) {
if src == nil {
return
}
if *dst == nil {
*dst = map[string]string{}
}
for k, v := range src {
(*dst)[k] = v
}
}
// AttachEntityMaps merges optional catalog/feed entity remaps into the document.
func (d *IDMapDocument) AttachEntityMaps(
categories, attributes, feeds, rawProducts, files map[string]string,
report map[string]int,
) {
if d == nil {
return
}
copyMap(&d.Categories, categories)
copyMap(&d.Attributes, attributes)
copyMap(&d.Feeds, feeds)
copyMap(&d.RawProducts, rawProducts)
copyMap(&d.Files, files)
d.Meta.UserCount = len(d.Users)
d.Meta.CompanyCount = len(d.Companies)
if report != nil {
d.Meta.Report = report
}
}
func NewIDMapDocument(users, companies map[string]string, dryRun bool) IDMapDocument {
if users == nil {
users = map[string]string{}
}
if companies == nil {
companies = map[string]string{}
}
return IDMapDocument{
Version: 1,
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
Source: "mysql",
Target: "postgres",
Users: users,
Companies: companies,
Meta: IDMapMeta{
UserCount: len(users),
CompanyCount: len(companies),
DryRun: dryRun,
},
}
}
func (d IDMapDocument) Validate() error {
if d.Version != 1 {
return fmt.Errorf("unsupported id map version %d", d.Version)
}
for legacy, id := range d.Users {
if legacy == "" {
return fmt.Errorf("empty user legacy id")
}
if _, err := uuid.Parse(id); err != nil {
return fmt.Errorf("user %q maps to invalid uuid %q", legacy, id)
}
}
for legacy, id := range d.Companies {
if legacy == "" {
return fmt.Errorf("empty company legacy id")
}
if _, err := uuid.Parse(id); err != nil {
return fmt.Errorf("company %q maps to invalid uuid %q", legacy, id)
}
}
return nil
}
func (d IDMapDocument) ResolveUser(legacy string) (uuid.UUID, bool) {
raw, ok := d.Users[legacy]
if !ok {
return uuid.Nil, false
}
id, err := uuid.Parse(raw)
if err != nil {
return uuid.Nil, false
}
return id, true
}
func (d IDMapDocument) ResolveCompany(legacy string) (uuid.UUID, bool) {
raw, ok := d.Companies[legacy]
if !ok {
return uuid.Nil, false
}
id, err := uuid.Parse(raw)
if err != nil {
return uuid.Nil, false
}
return id, true
}
func WriteIDMap(path string, d IDMapDocument) error {
if err := d.Validate(); err != nil {
return err
}
b, err := json.MarshalIndent(d, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0o644)
}
func ReadIDMap(path string) (IDMapDocument, error) {
b, err := os.ReadFile(path)
if err != nil {
return IDMapDocument{}, err
}
var d IDMapDocument
if err := json.Unmarshal(b, &d); err != nil {
return IDMapDocument{}, err
}
if err := d.Validate(); err != nil {
return IDMapDocument{}, err
}
return d, nil
}
func WriteIDMapDir(dir string, users, companies map[string]string, dryRun bool) (string, error) {
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", err
}
path := filepath.Join(dir, "id-map.json")
doc := NewIDMapDocument(users, companies, dryRun)
if err := WriteIDMap(path, doc); err != nil {
return "", err
}
return path, nil
}