From 3a7b07590fa1dc2e1a6b40eadf477dc0f3427961 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Thu, 11 Jun 2026 16:56:10 -0500 Subject: [PATCH] fix: wiki API sub-page support and content response (#606, #607) - Change wiki API routes from {pageName} to wildcard (*) to support pages with path separators (e.g. mcp/fleet-overview) - Use ListEntriesRecursiveFast() in ListWikiPages to include pages in subdirectories, not just root-level files - Add error logging in wikiContentsByEntry for diagnosing empty content_base64 responses - Fix pagination to count only regular files, not directories Co-Authored-By: Moko Consulting --- routers/api/v1/api.go | 4 ++-- routers/api/v1/repo/wiki.go | 34 +++++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c25d6a1fdd..cd24cf4a13 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1306,11 +1306,11 @@ func Routes() *web.Router { m.Combo("/{timetrackingusername}").Get(repo.ListTrackedTimesByUser) }, mustEnableIssues, reqToken()) m.Group("/wiki", func() { - m.Combo("/page/{pageName}"). + m.Combo("/page/*"). Get(repo.GetWikiPage). Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage). Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage) - m.Get("/revisions/{pageName}", repo.ListPageRevisions) + m.Get("/revisions/*", repo.ListPageRevisions) m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage) m.Get("/pages", repo.ListWikiPages) }, mustEnableWiki) diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index f55cce2ebc..d629f65712 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -12,6 +12,7 @@ import ( repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/gitrepo" + "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting" api "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/util" @@ -136,7 +137,7 @@ func EditWikiPage(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateWikiPageOptions) - oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName")) + oldWikiName := wiki_service.WebPathFromRequest(ctx.PathParam("*")) newWikiName := wiki_service.UserTitleToWebPath("", form.Title) if len(newWikiName) == 0 { @@ -242,7 +243,7 @@ func DeleteWikiPage(ctx *context.APIContext) { // "423": // "$ref": "#/responses/repoArchivedError" - wikiName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName")) + wikiName := wiki_service.WebPathFromRequest(ctx.PathParam("*")) if err := wiki_service.DeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, wikiName); err != nil { if err.Error() == "file does not exist" { @@ -307,14 +308,23 @@ func ListWikiPages(ctx *context.APIContext) { skip := (page - 1) * limit maxNum := page * limit - entries, err := commit.ListEntries() + entries, err := commit.ListEntriesRecursiveFast() if err != nil { ctx.APIErrorInternal(err) return } - pages := make([]*api.WikiPageMetaData, 0, len(entries)) - for i, entry := range entries { - if i < skip || i >= maxNum || !entry.IsRegular() { + + // Filter to regular files only and count for pagination. + var regularEntries []*git.TreeEntry + for _, entry := range entries { + if entry.IsRegular() { + regularEntries = append(regularEntries, entry) + } + } + + pages := make([]*api.WikiPageMetaData, 0, min(len(regularEntries), limit)) + for i, entry := range regularEntries { + if i < skip || i >= maxNum { continue } c, err := wikiRepo.GetCommitByPath(entry.Name()) @@ -333,8 +343,8 @@ func ListWikiPages(ctx *context.APIContext) { pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository)) } - ctx.SetLinkHeader(int64(len(entries)), limit) - ctx.SetTotalCountHeader(int64(len(entries))) + ctx.SetLinkHeader(int64(len(regularEntries)), limit) + ctx.SetTotalCountHeader(int64(len(regularEntries))) ctx.JSON(http.StatusOK, pages) } @@ -368,7 +378,7 @@ func GetWikiPage(ctx *context.APIContext) { // "$ref": "#/responses/notFound" // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName")) + pageName := wiki_service.WebPathFromRequest(ctx.PathParam("*")) wikiPage := getWikiPage(ctx, pageName) if !ctx.Written() { @@ -418,7 +428,7 @@ func ListPageRevisions(ctx *context.APIContext) { } // get requested pagename - pageName := wiki_service.WebPathFromRequest(ctx.PathParamRaw("pageName")) + pageName := wiki_service.WebPathFromRequest(ctx.PathParam("*")) if len(pageName) == 0 { pageName = "Home" } @@ -503,7 +513,9 @@ func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { } content, err := blob.GetBlobContentBase64(nil) if err != nil { - ctx.APIErrorInternal(err) + // Return the error details but don't abort — the page metadata + // is still useful even without content. + log.Error("wikiContentsByEntry: GetBlobContentBase64 for %s: %v", entry.Name(), err) return "" } return content -- 2.52.0