Refactor: improve backend modularity
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
// Path: Backend/internal/bootstrap/project_disk_items.go
|
||||
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func (service *Service) createProjectTreeItemOnDisk(projectSlug, parentFolderPath, name, itemType 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("item name is required")
|
||||
}
|
||||
canonicalItemType := normalizeProjectTreeItemType(itemType)
|
||||
containerProjectionPath := projectTreeRootPath(projectSlug)
|
||||
parentDir := filepath.Join(posixRoot, filepath.FromSlash(containerProjectionPath))
|
||||
if trimmedParentFolderPath := strings.TrimSpace(parentFolderPath); trimmedParentFolderPath != "" {
|
||||
containerProjectionPath = trimmedParentFolderPath
|
||||
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 item path: %w", err)
|
||||
}
|
||||
baseSlug := normalizePOSIXSlug(trimmedName)
|
||||
itemDirName := slugDir("item", baseSlug)
|
||||
itemDir := filepath.Join(parentDir, itemDirName)
|
||||
itemSlug := baseSlug
|
||||
for attempt := 2; ; attempt += 1 {
|
||||
if _, err := os.Stat(itemDir); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", fmt.Errorf("stat candidate project item: %w", err)
|
||||
}
|
||||
itemSlug = fmt.Sprintf("%s-%d", baseSlug, attempt)
|
||||
itemDirName = slugDir("item", itemSlug)
|
||||
itemDir = filepath.Join(parentDir, itemDirName)
|
||||
}
|
||||
if err := os.MkdirAll(itemDir, 0o755); err != nil {
|
||||
return "", fmt.Errorf("create project item: %w", err)
|
||||
}
|
||||
itemID := uuid.NewString()
|
||||
if err := writeJSONFile(filepath.Join(itemDir, "item.json"), map[string]any{"id": itemID, "name": trimmedName, "slug": itemSlug, "type": canonicalItemType}); err != nil {
|
||||
return "", fmt.Errorf("write project item.json: %w", err)
|
||||
}
|
||||
if err := writeJSONFile(filepath.Join(itemDir, "schema.json"), defaultProjectTreeItemSchema(canonicalItemType)); err != nil {
|
||||
return "", fmt.Errorf("write project schema.json: %w", err)
|
||||
}
|
||||
if err := writeJSONFile(filepath.Join(itemDir, "data.json"), defaultProjectTreeItemData(canonicalItemType, trimmedName)); err != nil {
|
||||
return "", fmt.Errorf("write project data.json: %w", err)
|
||||
}
|
||||
return filepath.ToSlash(filepath.Join(containerProjectionPath, itemDirName)), nil
|
||||
}
|
||||
|
||||
func (service *Service) deleteProjectTreeItemOnDisk(projectSlug, itemPath string) (string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
rootProjectionPath := projectTreeRootPath(projectSlug)
|
||||
itemProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+strings.TrimSpace(itemPath))), "/")
|
||||
if itemProjectionPath == "." || itemProjectionPath == rootProjectionPath || !strings.HasPrefix(itemProjectionPath, rootProjectionPath+"/") {
|
||||
return "", ErrProjectItemNotFound
|
||||
}
|
||||
itemDir := filepath.Join(posixRoot, filepath.FromSlash(itemProjectionPath))
|
||||
info, err := os.Stat(itemDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", ErrProjectItemNotFound
|
||||
}
|
||||
return "", fmt.Errorf("stat project item: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", ErrProjectItemNotFound
|
||||
}
|
||||
if err := os.RemoveAll(itemDir); err != nil {
|
||||
return "", fmt.Errorf("delete project item: %w", err)
|
||||
}
|
||||
return itemProjectionPath, nil
|
||||
}
|
||||
|
||||
func (service *Service) moveProjectTreeItemOnDisk(projectSlug, itemPath, parentFolderPath string) (string, string, error) {
|
||||
posixRoot := strings.TrimSpace(service.posixRoot)
|
||||
if posixRoot == "" {
|
||||
return "", "", fmt.Errorf("POSIX root is not configured")
|
||||
}
|
||||
rootProjectionPath := projectTreeRootPath(projectSlug)
|
||||
itemProjectionPath := strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+strings.TrimSpace(itemPath))), "/")
|
||||
if itemProjectionPath == "." || itemProjectionPath == rootProjectionPath || !strings.HasPrefix(itemProjectionPath, rootProjectionPath+"/") {
|
||||
return "", "", ErrProjectItemNotFound
|
||||
}
|
||||
itemDir := filepath.Join(posixRoot, filepath.FromSlash(itemProjectionPath))
|
||||
info, err := os.Stat(itemDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectItemNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat project item: %w", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return "", "", ErrProjectItemNotFound
|
||||
}
|
||||
trimmedParentFolderPath := strings.TrimSpace(parentFolderPath)
|
||||
parentProjectionPath := rootProjectionPath
|
||||
parentDir := filepath.Join(posixRoot, filepath.FromSlash(parentProjectionPath))
|
||||
if trimmedParentFolderPath != "" {
|
||||
parentProjectionPath = strings.TrimPrefix(filepath.ToSlash(filepath.Clean("/"+trimmedParentFolderPath)), "/")
|
||||
if parentProjectionPath == "." || parentProjectionPath == rootProjectionPath || !strings.HasPrefix(parentProjectionPath, rootProjectionPath+"/") {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
parentDir = filepath.Join(posixRoot, filepath.FromSlash(parentProjectionPath))
|
||||
}
|
||||
parentInfo, err := os.Stat(parentDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
return "", "", fmt.Errorf("stat project item parent: %w", err)
|
||||
}
|
||||
if !parentInfo.IsDir() {
|
||||
return "", "", ErrProjectFolderNotFound
|
||||
}
|
||||
currentParentDir := filepath.Dir(itemDir)
|
||||
currentBase := filepath.Base(itemDir)
|
||||
itemPayload := readJSONFileMap(filepath.Join(itemDir, "item.json"))
|
||||
itemID, _ := itemPayload["id"].(string)
|
||||
if strings.TrimSpace(itemID) == "" {
|
||||
itemID = uuid.NewString()
|
||||
}
|
||||
itemName, _ := itemPayload["name"].(string)
|
||||
if strings.TrimSpace(itemName) == "" {
|
||||
itemName = fallbackItemLabel(itemProjectionPath)
|
||||
}
|
||||
itemType, _ := itemPayload["type"].(string)
|
||||
canonicalItemType := normalizeProjectTreeItemType(itemType)
|
||||
baseSlug := strings.TrimPrefix(currentBase, "item-")
|
||||
if strings.TrimSpace(baseSlug) == "" {
|
||||
baseSlug = normalizePOSIXSlug(itemName)
|
||||
}
|
||||
itemSlug := baseSlug
|
||||
itemDirName := slugDir("item", itemSlug)
|
||||
destinationDir := filepath.Join(parentDir, itemDirName)
|
||||
for attempt := 2; ; attempt += 1 {
|
||||
if samePath(destinationDir, itemDir) {
|
||||
break
|
||||
}
|
||||
if _, err := os.Stat(destinationDir); os.IsNotExist(err) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return "", "", fmt.Errorf("stat candidate moved project item: %w", err)
|
||||
}
|
||||
itemSlug = fmt.Sprintf("%s-%d", baseSlug, attempt)
|
||||
itemDirName = slugDir("item", itemSlug)
|
||||
destinationDir = filepath.Join(parentDir, itemDirName)
|
||||
}
|
||||
movedProjectionPath := filepath.ToSlash(filepath.Join(parentProjectionPath, itemDirName))
|
||||
if samePath(currentParentDir, parentDir) && currentBase == itemDirName {
|
||||
return itemProjectionPath, itemProjectionPath, nil
|
||||
}
|
||||
if err := os.Rename(itemDir, destinationDir); err != nil {
|
||||
return "", "", fmt.Errorf("move project item: %w", err)
|
||||
}
|
||||
itemPayload["id"] = itemID
|
||||
itemPayload["name"] = itemName
|
||||
itemPayload["slug"] = itemSlug
|
||||
itemPayload["type"] = canonicalItemType
|
||||
if err := writeJSONFile(filepath.Join(destinationDir, "item.json"), itemPayload); err != nil {
|
||||
return "", "", fmt.Errorf("write moved project item.json: %w", err)
|
||||
}
|
||||
return itemProjectionPath, movedProjectionPath, nil
|
||||
}
|
||||
Reference in New Issue
Block a user