From ff264f0d7c5df8aebf70b4d6fbc3bc28643f9ab5 Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Tue, 9 Jun 2026 20:20:34 +0200 Subject: [PATCH] fix: collision prevent --- internal/topdata/wiki_deploy.go | 57 +++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/internal/topdata/wiki_deploy.go b/internal/topdata/wiki_deploy.go index 8f87e46..777709d 100644 --- a/internal/topdata/wiki_deploy.go +++ b/internal/topdata/wiki_deploy.go @@ -760,8 +760,16 @@ func shouldCheckRemoteTopicTitle(previous wikiDeployManifestPage, pageTitle, des func recoverCreateCollision(plan wikiDeployPlan, manifest wikiDeployManifest, client *nodeBBClient, summary string) (bool, error) { entry, ok, err := findExistingNodeBBPage(plan.Page, plan.Entry.CID, client) - if err != nil || !ok { - return ok, err + if err != nil { + return false, err + } + if !ok { + // Search-based lookup failed. NodeBB confirmed the collision, so the page definitely + // exists — fall back to listing the category and matching by title. + entry, ok, err = findCollisionPageByTitle(plan.Page, plan.Entry.CID, client) + if err != nil || !ok { + return false, err + } } remote, err := client.getPost(entry.PID) if err != nil { @@ -801,6 +809,15 @@ func adoptExistingNodeBBPage(page wikiDeployPage, cid int, remotePagesByCID map[ if err := nestedCanonicalAdoptionFallbackError(page, remotePages); err != nil { return wikiDeployManifestPage{}, false, err } + // For nested-path pages: nestedCanonicalAdoptionFallbackError only examined pages + // without WikiPath/CanonicalPath. Pages that have those fields set but whose stored + // path doesn't normalize to match the local PublicPath (e.g. different slug format) + // still need to be adopted. Try an unambiguous title match across all remote pages. + if isNestedCanonicalWikiPath(page.PublicPath) { + if remotePage, ok := matchByTitle(page, remotePages); ok && remotePage.TID != 0 { + return fetchNodeBBWikiPageMapping(page, cid, remotePage, client) + } + } return wikiDeployManifestPage{}, false, nil } return fetchNodeBBWikiPageMapping(page, cid, remotePage, client) @@ -834,6 +851,42 @@ func findExistingNodeBBPage(page wikiDeployPage, cid int, client *nodeBBClient) return wikiDeployManifestPage{}, false, nil } +func findCollisionPageByTitle(page wikiDeployPage, cid int, client *nodeBBClient) (wikiDeployManifestPage, bool, error) { + remotePages, err := client.listNamespacePages(cid) + if err != nil { + return wikiDeployManifestPage{}, false, err + } + remotePage, ok := matchByTitle(page, remotePages) + if !ok || remotePage.TID == 0 { + return wikiDeployManifestPage{}, false, nil + } + return fetchNodeBBWikiPageMapping(page, cid, remotePage, client) +} + +func matchByTitle(page wikiDeployPage, remotePages []nodeBBWikiPage) (nodeBBWikiPage, bool) { + desiredTitle := normalizeWikiTitle(page.Title) + if desiredTitle == "" { + return nodeBBWikiPage{}, false + } + var match *nodeBBWikiPage + for i, candidate := range remotePages { + for _, title := range []string{candidate.TitleLeaf, candidate.Title} { + if normalizeWikiTitle(title) == desiredTitle { + if match != nil { + return nodeBBWikiPage{}, false // ambiguous — refuse to guess + } + cp := remotePages[i] + match = &cp + break + } + } + } + if match == nil { + return nodeBBWikiPage{}, false + } + return *match, true +} + func uniqueNodeBBPageSearchQueries(values ...string) []string { queries := []string{} seen := map[string]struct{}{}