Files
descrybe/apps/api/cmd/migrator/files.go
T

110 lines
3.0 KiB
Go
Raw Normal View History

package main
import (
"context"
"database/sql"
"encoding/json"
"log"
"strconv"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
// migrateFiles copies file *metadata* only.
//
// Blobs strategy (cutover):
// - Do NOT stream MySQL/local blob bytes through the migrator.
// - Preserve legacy url/path in files.path and legacy id in metadata._legacy_file_id.
// - Operators re-attach object storage / local volumes under the same relative paths,
// or run a separate rsync/S3 sync keyed by legacy id after DNS freeze.
// - raw_products.file_id is left unset until a follow-up remapper exists.
func migrateFiles(
ctx context.Context,
mysqlDB *sql.DB,
pg *pgxpool.Pool,
companyMap, userMap, fileMap map[string]string,
allow map[string]bool,
report map[string]int,
dryRun bool,
) {
if !mysqlTableExists(ctx, mysqlDB, "files") {
log.Printf("files skipped: table missing (blobs strategy: metadata-only when present)")
return
}
clause, cargs := mysqlCompanyFilter("company_id", allow)
rows, err := mysqlDB.QueryContext(ctx, `
SELECT id, company_id, COALESCE(user_id, ''), file_name,
COALESCE(file_type, ''), COALESCE(file_size, 0),
COALESCE(status, 'uploaded'), url, metadata
FROM files WHERE 1=1`+clause, cargs...)
if err != nil {
// Older dumps may lack metadata/url/status.
rows, err = mysqlDB.QueryContext(ctx, `
SELECT id, company_id, COALESCE(user_id, ''), file_name,
COALESCE(file_type, ''), COALESCE(file_size, 0),
'uploaded', NULL, NULL
FROM files WHERE 1=1`+clause, cargs...)
}
if err != nil {
log.Printf("files skipped: %v", err)
return
}
defer rows.Close()
for rows.Next() {
var legacyID int64
var companyLegacy, userLegacy, name, fileType, status string
var size int64
var url sql.NullString
var metadata []byte
if err := rows.Scan(&legacyID, &companyLegacy, &userLegacy, &name, &fileType, &size, &status, &url, &metadata); err != nil {
report["files_skipped"]++
continue
}
cid, ok := companyMap[companyLegacy]
if !ok {
report["files_skipped"]++
continue
}
meta := map[string]any{}
if len(metadata) > 0 {
_ = json.Unmarshal(metadata, &meta)
}
meta["_legacy_file_id"] = legacyID
meta["_blob_strategy"] = "metadata_only_resync_paths"
if fileType != "" {
meta["file_type"] = fileType
}
metaBytes, _ := json.Marshal(meta)
newID := uuid.New()
legacyKey := strconv.FormatInt(legacyID, 10)
fileMap[legacyKey] = newID.String()
var uid *string
if userLegacy != "" {
if mapped, ok := userMap[userLegacy]; ok {
uid = &mapped
}
}
if dryRun {
report["files"]++
continue
}
_, err = pg.Exec(ctx, `
INSERT INTO files (id, company_id, user_id, name, path, content_type, size_bytes, status, metadata)
VALUES ($1, $2, $3::uuid, $4, $5, $6, $7, $8, $9::jsonb)`,
newID, cid, uid, name, nullString(url), nullStr(fileType), size, status, string(metaBytes))
if err != nil {
log.Printf("files insert %d: %v", legacyID, err)
delete(fileMap, legacyKey)
report["files_skipped"]++
continue
}
report["files"]++
}
}