2
0

feat(repo): add hidden folders feature for code browser

Allow repository admins to hide specific folders from the code browser for non-admin users. Hidden folders are shown dimmed to admins but completely hidden from regular users. Includes database migration, settings UI, tree filtering logic, and frontend support for toggling visibility.
This commit is contained in:
2026-01-26 22:40:03 -05:00
parent b0b1b12fd5
commit 72d282cfaa
18 changed files with 390 additions and 10 deletions

View File

@@ -414,6 +414,7 @@ func prepareMigrationTasks() []*migration {
newMigration(337, "Add is_private to package for package visibility", v1_26.AddIsPrivateToPackage),
newMigration(338, "Add deleted_unix to action_runner table", v1_26.AddDeletedUnixToActionRunner),
newMigration(339, "Add is_limited to repository for limited visibility", v1_26.AddIsLimitedToRepository),
newMigration(340, "Create repo_hidden_folder table for hidden folders", v1_26.CreateRepoHiddenFolderTable),
}
return preparedMigrations
}

View File

@@ -0,0 +1,23 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_26
import (
"code.gitcaddy.com/server/v3/modules/timeutil"
"xorm.io/xorm"
)
// CreateRepoHiddenFolderTable creates the repo_hidden_folder table.
// Hidden folders are not shown in the code browser for non-admin users.
func CreateRepoHiddenFolderTable(x *xorm.Engine) error {
type RepoHiddenFolder struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
FolderPath string `xorm:"UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
return x.Sync(new(RepoHiddenFolder))
}

View File

@@ -0,0 +1,83 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"context"
"strings"
"code.gitcaddy.com/server/v3/models/db"
"code.gitcaddy.com/server/v3/modules/timeutil"
)
// RepoHiddenFolder represents a folder that is hidden from the code browser
// for non-admin users.
type RepoHiddenFolder struct {
ID int64 `xorm:"pk autoincr"`
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
FolderPath string `xorm:"UNIQUE(s) NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}
func init() {
db.RegisterModel(new(RepoHiddenFolder))
}
// GetHiddenFolders returns all hidden folders for a repository.
func GetHiddenFolders(ctx context.Context, repoID int64) ([]*RepoHiddenFolder, error) {
folders := make([]*RepoHiddenFolder, 0)
return folders, db.GetEngine(ctx).Where("repo_id = ?", repoID).OrderBy("folder_path").Find(&folders)
}
// GetHiddenFolderPaths returns a set of hidden folder paths for fast lookup.
func GetHiddenFolderPaths(ctx context.Context, repoID int64) (map[string]bool, error) {
folders, err := GetHiddenFolders(ctx, repoID)
if err != nil {
return nil, err
}
paths := make(map[string]bool, len(folders))
for _, f := range folders {
paths[f.FolderPath] = true
}
return paths, nil
}
// IsHiddenFolder checks if a specific folder path is hidden.
func IsHiddenFolder(ctx context.Context, repoID int64, folderPath string) (bool, error) {
return db.GetEngine(ctx).Where("repo_id = ? AND folder_path = ?", repoID, folderPath).Exist(&RepoHiddenFolder{})
}
// AddHiddenFolder adds a folder to the hidden list.
func AddHiddenFolder(ctx context.Context, repoID int64, folderPath string) error {
_, err := db.GetEngine(ctx).Insert(&RepoHiddenFolder{
RepoID: repoID,
FolderPath: folderPath,
})
return err
}
// RemoveHiddenFolder removes a folder from the hidden list.
func RemoveHiddenFolder(ctx context.Context, repoID int64, folderPath string) error {
_, err := db.GetEngine(ctx).Where("repo_id = ? AND folder_path = ?", repoID, folderPath).Delete(&RepoHiddenFolder{})
return err
}
// IsPathUnderHiddenFolder checks if a path or any of its parent paths is hidden.
func IsPathUnderHiddenFolder(hiddenPaths map[string]bool, targetPath string) bool {
if hiddenPaths[targetPath] {
return true
}
// Check parent paths
for {
idx := strings.LastIndex(targetPath, "/")
if idx < 0 {
break
}
targetPath = targetPath[:idx]
if hiddenPaths[targetPath] {
return true
}
}
return false
}