feat(cli): add URL prefix fix command - #1600
Conversation
closes apache#861 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new answer fix CLI command to rewrite stored URL prefixes across branding assets, user avatars, and post content (including revisions and file records), with support for dry-run and a global row limit. This fits into the codebase as an administrative maintenance tool for safely migrating stored paths (e.g., /uploads/ → /cdn/) while Answer is stopped.
Changes:
- Introduces URL-prefix classification and batched DB rewrite logic for branding, avatars, posts/revisions, and
file_recordURLs. - Adds cache invalidation for branding site-info when using file-backed cache storage.
- Wires a new Cobra command (
answer fix ...) with--dry-runand--limitflags.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/cli/fix.go | Core fix orchestration, prefix classification/replacement utilities, batching, and summary output. |
| internal/cli/fix_post.go | Applies prefix fixes to question/answer bodies, revisions, and post-related file records. |
| internal/cli/fix_filerecord.go | Updates file_record.file_url for selected sources. |
| internal/cli/fix_cache.go | Invalidates branding site-info cache in file-backed memory cache. |
| internal/cli/fix_branding.go | Fixes branding JSON fields in site_info and related branding file records. |
| internal/cli/fix_avatar.go | Fixes custom avatar URLs in user avatar JSON and related avatar file records. |
| cmd/command.go | Adds the fix command and its flags to the CLI. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if err := json.Unmarshal([]byte(user.Avatar), avatar); err != nil { | ||
| return nil | ||
| } |
| switch classifyText(texts, opts.SrcPrefix, opts.DstPrefix, markers) { | ||
| case matchBoth: | ||
| fmt.Printf("[%s] WARN %s: has BOTH prefixes, skipping (manual fix needed)\n", label, target) | ||
| result.Skipped++ | ||
| return nil | ||
| case matchSkip: | ||
| fmt.Printf("[%s] skip %s: already has DST_PREFIX\n", label, target) | ||
| result.Skipped++ | ||
| return nil | ||
| case matchNone: | ||
| return nil | ||
| } |
|
|
||
| // classifyText classifies free text that may embed URLs at marker positions. | ||
| func classifyText(texts []string, src, dst string, markers []string) prefixMatch { | ||
| hasSrc := anyContainsPrefixNotShadowed(texts, src, dst, markers) |
There was a problem hiding this comment.
classifyText aggregates prefix matches across all supplied fields. For a post, this means a record is skipped when (for example) original_text still contains SRC_PREFIX while parsed_text already contains DST_PREFIX.
This may be uncommon in normal flows, since these fields are usually updated together, but it can still occur after an interrupted/manual migration or from pre-existing inconsistent data. In that case, skipping the whole record leaves the old URL behind even though neither individual field contains both prefixes.
To stay consistent with the command description (“If a text field contains both prefixes…”), could we classify and guard each text field independently, then replace only the fields that still contain SRC_PREFIX?
I've tested locally with fake data generated in sqlite db, including:
It took about 5 minutes to complete the fixing.