Refactor: improve backend modularity
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
// Path: Backend/internal/bootstrap/project_disk_folders.go
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (service *Service) createProjectFolderOnDisk(projectSlug, parentFolderID, name string, rootPathBuilder func(projectSlug string) string) (string, string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
trimmedName := strings.TrimSpace(name)
|
||||
if trimmedName == "" {
|
||||
return "", "", fmt.Errorf("folder name is required")
|
||||
}
|
||||
containerProjectionPath := rootPathBuilder(projectSlug)
|
||||
parentDir := filepath.Join(posixRoot, filepath.FromSlash(containerProjectionPath))
|
||||
if strings.TrimSpace(parentFolderID) != "" {
|
||||
containerProjectionPath = filepath.ToSlash(filepath.Join(strings.TrimSpace(parentFolderID), "children"))
|
||||
parentDir = filepath.Join(posixRoot, filepath.FromSlash(containerProjectionPath))
|
||||
info, err := os.Stat(parentDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat parent project folder: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
}
|
||||
if err := os.MkdirAll(parentDir, 0o755); err != nil {
|
||||
return "", "", fmt.Errorf("create parent project folder path: %w", err)
|
||||
}
|
||||
baseSlug := normalizePOSIXSlug(trimmedName)
|
||||
folderName := slugDir("folder", baseSlug)
|
||||
folderDir := filepath.Join(parentDir, folderName)
|
||||
folderSlug := baseSlug
|
||||
for attempt := 2; ; attempt += 1 {
|
||||
if _, err := os.Stat(folderDir); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", "", fmt.Errorf("stat candidate project folder: %w", err)
|
||||
}
|
||||
folderSlug = fmt.Sprintf("%s-%d", baseSlug, attempt)
|
||||
folderName = slugDir("folder", folderSlug)
|
||||
folderDir = filepath.Join(parentDir, folderName)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(folderDir, "children"), 0o755); err != nil {
|
||||
return "", "", fmt.Errorf("create project hierarchy folder: %w", err)
|
||||
}
|
||||
folderID := uuid.NewString()
|
||||
if err := writeJSONFile(filepath.Join(folderDir, "folder.json"), map[string]any{"id": folderID, "name": trimmedName, "slug": folderSlug, "type": "folder"}); err != nil {
|
||||
return "", "", fmt.Errorf("write project folder.json: %w", err)
|
||||
}
|
||||
if err := writeJSONFile(filepath.Join(folderDir, "acl.json"), map[string]any{"version": 1, "inherits": true, "rules": []any{}}); err != nil {
|
||||
return "", "", fmt.Errorf("write project acl.json: %w", err)
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join(containerProjectionPath, folderName)), folderSlug, nil
|
||||
}
|
||||
|
||||
func (service *Service) deleteProjectFolderOnDisk(projectSlug, folderID string, rootPathBuilder func(projectSlug string) string) (string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
trimmedFolderID := strings.TrimSpace(folderID)
|
||||
if trimmedFolderID == "" {
|
||||
return "", ErrProjectFolderNotFound
|
||||
}
|
||||
rootProjectionPath := rootPathBuilder(projectSlug)
|
||||
folderProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+trimmedFolderID)), "/")
|
||||
if folderProjectionPath == "." || folderProjectionPath == rootProjectionPath || !strings.HasPrefix(folderProjectionPath, rootProjectionPath+"/") {
|
||||
return "", ErrProjectFolderNotFound
|
||||
}
|
||||
folderDir := filepath.Join(posixRoot, filepath.FromSlash(folderProjectionPath))
|
||||
info, err := os.Stat(folderDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", fmt.Errorf("stat project folder: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", ErrProjectFolderNotFound
|
||||
}
|
||||
if err := os.RemoveAll(folderDir); err != nil {
|
||||
return "", fmt.Errorf("delete project folder: %w", err)
|
||||
}
|
||||
return folderProjectionPath, nil
|
||||
}
|
||||
|
||||
func (service *Service) renameProjectFolderOnDisk(projectSlug, folderID, name string, rootPathBuilder func(projectSlug string) string) (string, string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
trimmedFolderID := strings.TrimSpace(folderID)
|
||||
if trimmedFolderID == "" {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
trimmedName := strings.TrimSpace(name)
|
||||
if trimmedName == "" {
|
||||
return "", "", fmt.Errorf("folder name is required")
|
||||
}
|
||||
rootProjectionPath := rootPathBuilder(projectSlug)
|
||||
folderProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+trimmedFolderID)), "/")
|
||||
if folderProjectionPath == "." || folderProjectionPath == rootProjectionPath || !strings.HasPrefix(folderProjectionPath, rootProjectionPath+"/") {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
folderDir := filepath.Join(posixRoot, filepath.FromSlash(folderProjectionPath))
|
||||
info, err := os.Stat(folderDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat project folder: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
parentDir := filepath.Dir(folderDir)
|
||||
baseSlug := normalizePOSIXSlug(trimmedName)
|
||||
folderName := slugDir("folder", baseSlug)
|
||||
folderSlug := baseSlug
|
||||
destinationDir := filepath.Join(parentDir, folderName)
|
||||
for attempt := 2; ; attempt += 1 {
|
||||
if destinationDir == folderDir {
|
||||
break
|
||||
}
|
||||
if _, err := os.Stat(destinationDir); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", "", fmt.Errorf("stat candidate renamed project folder: %w", err)
|
||||
}
|
||||
folderSlug = fmt.Sprintf("%s-%d", baseSlug, attempt)
|
||||
folderName = slugDir("folder", folderSlug)
|
||||
destinationDir = filepath.Join(parentDir, folderName)
|
||||
}
|
||||
renamedProjectionPath := filepath.ToSlash(filepath.Join(filepath.Dir(folderProjectionPath), folderName))
|
||||
if destinationDir != folderDir {
|
||||
if err := os.Rename(folderDir, destinationDir); err != nil {
|
||||
return "", "", fmt.Errorf("rename project folder: %w", err)
|
||||
}
|
||||
}
|
||||
folderPayload := readJSONFileMap(filepath.Join(destinationDir, "folder.json"))
|
||||
folderMetadataID, _ := folderPayload["id"].(string)
|
||||
if strings.TrimSpace(folderMetadataID) == "" {
|
||||
folderMetadataID = uuid.NewString()
|
||||
}
|
||||
if err := writeJSONFile(filepath.Join(destinationDir, "folder.json"), map[string]any{"id": folderMetadataID, "name": trimmedName, "slug": folderSlug, "type": "folder"}); err != nil {
|
||||
return "", "", fmt.Errorf("write renamed project folder.json: %w", err)
|
||||
}
|
||||
return folderProjectionPath, renamedProjectionPath, nil
|
||||
}
|
||||
|
||||
func (service *Service) moveProjectFolderOnDisk(projectSlug, folderID, parentFolderID string, rootPathBuilder func(projectSlug string) string) (string, string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
rootProjectionPath := rootPathBuilder(projectSlug)
|
||||
folderProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+strings.TrimSpace(folderID))), "/")
|
||||
if folderProjectionPath == "." || folderProjectionPath == rootProjectionPath || !strings.HasPrefix(folderProjectionPath, rootProjectionPath+"/") {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
folderDir := filepath.Join(posixRoot, filepath.FromSlash(folderProjectionPath))
|
||||
info, err := os.Stat(folderDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat project folder: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
trimmedParentFolderID := strings.TrimSpace(parentFolderID)
|
||||
parentChildrenProjectionPath := rootProjectionPath
|
||||
parentDir := filepath.Join(posixRoot, filepath.FromSlash(rootProjectionPath))
|
||||
if trimmedParentFolderID != "" {
|
||||
parentProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+trimmedParentFolderID)), "/")
|
||||
if parentProjectionPath == "." || parentProjectionPath == rootProjectionPath || !strings.HasPrefix(parentProjectionPath, rootProjectionPath+"/") {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
if parentProjectionPath == folderProjectionPath || strings.HasPrefix(parentProjectionPath, folderProjectionPath+"/children/") {
|
||||
return "", "", ErrInvalidProjectFolderMove
|
||||
}
|
||||
parentChildrenProjectionPath = filepath.ToSlash(filepath.Join(parentProjectionPath, "children"))
|
||||
parentDir = filepath.Join(posixRoot, filepath.FromSlash(parentChildrenProjectionPath))
|
||||
}
|
||||
parentInfo, err := os.Stat(parentDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat project folder parent: %w", err)
|
||||
}
|
||||
if !parentInfo.IsDir() {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
currentParentDir := filepath.Dir(folderDir)
|
||||
if samePath(currentParentDir, parentDir) {
|
||||
return folderProjectionPath, folderProjectionPath, nil
|
||||
}
|
||||
folderPayload := readJSONFileMap(filepath.Join(folderDir, "folder.json"))
|
||||
folderMetadataID, _ := folderPayload["id"].(string)
|
||||
if strings.TrimSpace(folderMetadataID) == "" {
|
||||
folderMetadataID = uuid.NewString()
|
||||
}
|
||||
folderName, _ := folderPayload["name"].(string)
|
||||
if strings.TrimSpace(folderName) == "" {
|
||||
folderName = fallbackFolderLabel(folderProjectionPath)
|
||||
}
|
||||
currentBase := filepath.Base(folderDir)
|
||||
baseSlug := strings.TrimPrefix(currentBase, "folder-")
|
||||
if strings.TrimSpace(baseSlug) == "" {
|
||||
baseSlug = normalizePOSIXSlug(folderName)
|
||||
}
|
||||
folderSlug := baseSlug
|
||||
folderDirName := slugDir("folder", folderSlug)
|
||||
destinationDir := filepath.Join(parentDir, folderDirName)
|
||||
for attempt := 2; ; attempt += 1 {
|
||||
if _, err := os.Stat(destinationDir); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", "", fmt.Errorf("stat candidate moved project folder: %w", err)
|
||||
}
|
||||
folderSlug = fmt.Sprintf("%s-%d", baseSlug, attempt)
|
||||
folderDirName = slugDir("folder", folderSlug)
|
||||
destinationDir = filepath.Join(parentDir, folderDirName)
|
||||
}
|
||||
if err := os.Rename(folderDir, destinationDir); err != nil {
|
||||
return "", "", fmt.Errorf("move project folder: %w", err)
|
||||
}
|
||||
folderPayload["id"] = folderMetadataID
|
||||
folderPayload["name"] = folderName
|
||||
folderPayload["slug"] = folderSlug
|
||||
folderPayload["type"] = "folder"
|
||||
if err := writeJSONFile(filepath.Join(destinationDir, "folder.json"), folderPayload); err != nil {
|
||||
return "", "", fmt.Errorf("write moved project folder.json: %w", err)
|
||||
}
|
||||
movedProjectionPath := filepath.ToSlash(filepath.Join(parentChildrenProjectionPath, folderDirName))
|
||||
return folderProjectionPath, movedProjectionPath, nil
|
||||
}
|
||||
Reference in New Issue
Block a user