2
0

feat(api): add hidden folders management endpoints to v2 API

Implements three new endpoints for managing repository hidden folders:
- GET /repos/{owner}/{repo}/hidden-folders - list all hidden folders
- PUT /repos/{owner}/{repo}/hidden-folders - add a hidden folder
- DELETE /repos/{owner}/{repo}/hidden-folders - remove a hidden folder

All write operations require repository admin permissions.
This commit is contained in:
2026-01-27 14:02:48 -05:00
parent c140847761
commit 7844b8d2a4
3 changed files with 155 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package structs
// HiddenFolderV2 represents a hidden folder in a repository.
type HiddenFolderV2 struct {
Path string `json:"path"`
CreatedAt int64 `json:"created_at"`
}
// HiddenFolderListV2 is the response for listing hidden folders.
type HiddenFolderListV2 struct {
Folders []*HiddenFolderV2 `json:"folders"`
}
// HiddenFolderOptionV2 is the request body for adding or removing a hidden folder.
type HiddenFolderOptionV2 struct {
Path string `json:"path" binding:"Required"`
}

View File

@@ -174,6 +174,15 @@ func Routes() *web.Router {
m.Get("/config", repoAssignmentWithPublicAccess(), GetPagesConfig)
m.Get("/content", repoAssignmentWithPublicAccess(), GetPagesContent)
})
// Hidden folders API - manage hidden folders for a repository
m.Group("/repos/{owner}/{repo}/hidden-folders", func() {
m.Get("", repoAssignment(), ListHiddenFoldersV2)
m.Group("", func() {
m.Put("", web.Bind(api.HiddenFolderOptionV2{}), AddHiddenFolderV2)
m.Delete("", web.Bind(api.HiddenFolderOptionV2{}), RemoveHiddenFolderV2)
}, repoAssignment(), reqToken())
})
})
return m

View File

@@ -0,0 +1,126 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package v2
import (
"net/http"
"strings"
repo_model "code.gitcaddy.com/server/v3/models/repo"
apierrors "code.gitcaddy.com/server/v3/modules/errors"
api "code.gitcaddy.com/server/v3/modules/structs"
"code.gitcaddy.com/server/v3/modules/web"
"code.gitcaddy.com/server/v3/services/context"
)
// ListHiddenFoldersV2 returns all hidden folders for a repository.
func ListHiddenFoldersV2(ctx *context.APIContext) {
folders, err := repo_model.GetHiddenFolders(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*api.HiddenFolderV2, 0, len(folders))
for _, f := range folders {
result = append(result, &api.HiddenFolderV2{
Path: f.FolderPath,
CreatedAt: int64(f.CreatedUnix),
})
}
ctx.JSON(http.StatusOK, &api.HiddenFolderListV2{Folders: result})
}
// AddHiddenFolderV2 hides a folder in a repository.
func AddHiddenFolderV2(ctx *context.APIContext) {
if !ctx.Repo.Permission.IsAdmin() && !ctx.IsUserSiteAdmin() {
ctx.APIErrorWithCode(apierrors.PermRepoAdminRequired)
return
}
form := web.GetForm(ctx).(*api.HiddenFolderOptionV2)
folderPath := strings.Trim(form.Path, "/")
if folderPath == "" || strings.Contains(folderPath, "..") {
ctx.APIErrorWithCode(apierrors.ValInvalidInput, map[string]any{
"field": "path",
"error": "path must not be empty or contain '..'",
})
return
}
isHidden, err := repo_model.IsHiddenFolder(ctx, ctx.Repo.Repository.ID, folderPath)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if isHidden {
ctx.APIErrorWithCode(apierrors.ResourceConflict, map[string]any{
"path": folderPath,
"error": "folder is already hidden",
})
return
}
if err := repo_model.AddHiddenFolder(ctx, ctx.Repo.Repository.ID, folderPath); err != nil {
ctx.APIErrorInternal(err)
return
}
// Fetch the created record to get the timestamp
folders, err := repo_model.GetHiddenFolders(ctx, ctx.Repo.Repository.ID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
for _, f := range folders {
if f.FolderPath == folderPath {
ctx.JSON(http.StatusCreated, &api.HiddenFolderV2{
Path: f.FolderPath,
CreatedAt: int64(f.CreatedUnix),
})
return
}
}
// Fallback if record not found (shouldn't happen)
ctx.JSON(http.StatusCreated, &api.HiddenFolderV2{Path: folderPath})
}
// RemoveHiddenFolderV2 unhides a folder in a repository.
func RemoveHiddenFolderV2(ctx *context.APIContext) {
if !ctx.Repo.Permission.IsAdmin() && !ctx.IsUserSiteAdmin() {
ctx.APIErrorWithCode(apierrors.PermRepoAdminRequired)
return
}
form := web.GetForm(ctx).(*api.HiddenFolderOptionV2)
folderPath := strings.Trim(form.Path, "/")
if folderPath == "" {
ctx.APIErrorWithCode(apierrors.ValInvalidInput, map[string]any{
"field": "path",
"error": "path must not be empty",
})
return
}
isHidden, err := repo_model.IsHiddenFolder(ctx, ctx.Repo.Repository.ID, folderPath)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if !isHidden {
ctx.APIErrorNotFound("folder is not hidden")
return
}
if err := repo_model.RemoveHiddenFolder(ctx, ctx.Repo.Repository.ID, folderPath); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}