package project import ( "fmt" "os" "path/filepath" "sort" ) // Store manages project directories under DataDir/projects. type Store interface { List() ([]Project, error) Ensure(id string) error Delete(id string) error } // DirStore discovers projects as subdirectories of dataDir/projects. type DirStore struct { dataDir string } // NewDirStore creates a project store rooted at dataDir. func NewDirStore(dataDir string) *DirStore { return &DirStore{dataDir: dataDir} } func (s *DirStore) projectsRoot() string { return filepath.Join(s.dataDir, "projects") } // List returns all project directories, sorted by name. func (s *DirStore) List() ([]Project, error) { root := s.projectsRoot() entries, err := os.ReadDir(root) if err != nil { if os.IsNotExist(err) { return []Project{}, nil } return nil, fmt.Errorf("list projects: %w", err) } var projects []Project for _, e := range entries { if !e.IsDir() { continue } name := e.Name() projects = append(projects, Project{ID: name, Name: name}) } sort.Slice(projects, func(i, j int) bool { return projects[i].Name < projects[j].Name }) return projects, nil } // Ensure creates the project directory if it does not exist. func (s *DirStore) Ensure(id string) error { if id == "" { return fmt.Errorf("project id is required") } dir := filepath.Join(s.projectsRoot(), id) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("create project %s: %w", id, err) } return nil } // Delete removes the project directory and all of its files. func (s *DirStore) Delete(id string) error { if id == "" { return fmt.Errorf("project id is required") } dir := filepath.Join(s.projectsRoot(), id) st, err := os.Stat(dir) if err != nil { if os.IsNotExist(err) { return fmt.Errorf("project %s: not found", id) } return fmt.Errorf("stat project %s: %w", id, err) } if !st.IsDir() { return fmt.Errorf("project %s: not a directory", id) } if err := os.RemoveAll(dir); err != nil { return fmt.Errorf("delete project %s: %w", id, err) } return nil }