package service import ( "context" "fmt" "sort" "strings" "gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository" ) // ScriptService handles script CRUD, tree building, and interpreter resolution. type ScriptService struct { Repo *repository.Repository InterpreterRepo *repository.ScriptInterpreterRepo } // NewScriptService creates a new ScriptService with both script and interpreter repos. func NewScriptService(repo *repository.Repository) *ScriptService { return &ScriptService{Repo: repo} } // NewScriptServiceWithInterpreters creates a ScriptService with interpreter support. func NewScriptServiceWithInterpreters(repo *repository.Repository, interpRepo *repository.ScriptInterpreterRepo) *ScriptService { return &ScriptService{Repo: repo, InterpreterRepo: interpRepo} } // treeNode is an internal representation for building the tree. type treeNode struct { name string typ string // "folder" or "file" children map[string]*treeNode // File-specific fields id *int64 content *string interpreterID *int64 } // BuildTree builds a directory tree from all scripts in the database. // Each script path is treated as a file path (e.g. "deploy/nginx/restart.sh"). func (s *ScriptService) BuildTree() ([]repository.ScriptTreeNode, error) { scripts, err := s.Repo.ListScripts() if err != nil { return nil, err } root := make(map[string]*treeNode) for _, sc := range scripts { parts := strings.Split(sc.Path, "/") // Walk through path parts, creating folders as needed currentMap := root for i, part := range parts { isFile := i == len(parts)-1 if _, exists := currentMap[part]; !exists { node := &treeNode{ name: part, children: make(map[string]*treeNode), } if isFile { node.typ = "file" id := sc.ID content := sc.Content interpreterID := sc.InterpreterID node.id = &id node.content = &content node.interpreterID = &interpreterID } else { node.typ = "folder" } currentMap[part] = node } currentMap = currentMap[part].children } } return buildTreeSlice(root), nil } // buildTreeSlice converts a map of treeNodes to a sorted slice of ScriptTreeNode. func buildTreeSlice(m map[string]*treeNode) []repository.ScriptTreeNode { result := make([]repository.ScriptTreeNode, 0, len(m)) for _, node := range m { result = append(result, toScriptTreeNode(node)) } // Sort: folders first, then files, alphabetically within each group sort.Slice(result, func(i, j int) bool { if result[i].Type != result[j].Type { return result[i].Type == "folder" } return result[i].Name < result[j].Name }) return result } // toScriptTreeNode converts a treeNode to a ScriptTreeNode with recursively converted children. func toScriptTreeNode(node *treeNode) repository.ScriptTreeNode { result := repository.ScriptTreeNode{ Name: node.name, Type: node.typ, Children: []repository.ScriptTreeNode{}, } if node.typ == "file" { result.ID = node.id result.Content = node.content result.InterpreterID = node.interpreterID } else { result.Children = buildTreeSlice(node.children) } return result } // ResolveCommand resolves the full command for a script using its interpreter. func (s *ScriptService) ResolveCommand(ctx context.Context, interpreterID int64, scriptText string) ([]string, error) { if s.InterpreterRepo == nil { return nil, fmt.Errorf("interpreter repo not configured") } interpreter, err := s.InterpreterRepo.GetByID(ctx, interpreterID) if err != nil { return nil, fmt.Errorf("get interpreter: %w", err) } // Build command: argv[0] argv[1] ... -c scriptText cmd := append(interpreter.Argv, "-c", scriptText) return cmd, nil } // List returns all interpreters. func (s *ScriptService) List(ctx context.Context) ([]repository.ScriptInterpreter, error) { if s.InterpreterRepo == nil { return nil, fmt.Errorf("interpreter repo not configured") } return s.InterpreterRepo.List(ctx) } // Create creates a new interpreter. func (s *ScriptService) Create(ctx context.Context, in repository.ScriptInterpreterCreate) (*repository.ScriptInterpreter, error) { if s.InterpreterRepo == nil { return nil, fmt.Errorf("interpreter repo not configured") } return s.InterpreterRepo.Create(ctx, in) } // GetByID returns an interpreter by ID. func (s *ScriptService) GetByID(ctx context.Context, id int64) (*repository.ScriptInterpreter, error) { if s.InterpreterRepo == nil { return nil, fmt.Errorf("interpreter repo not configured") } return s.InterpreterRepo.GetByID(ctx, id) } // Update updates an interpreter. func (s *ScriptService) Update(ctx context.Context, id int64, in repository.ScriptInterpreterUpdate) (*repository.ScriptInterpreter, error) { if s.InterpreterRepo == nil { return nil, fmt.Errorf("interpreter repo not configured") } return s.InterpreterRepo.Update(ctx, id, in) } // Delete deletes an interpreter. func (s *ScriptService) Delete(ctx context.Context, id int64) error { if s.InterpreterRepo == nil { return fmt.Errorf("interpreter repo not configured") } return s.InterpreterRepo.Delete(ctx, id) }