package lib import ( "encoding/json" "os" "path/filepath" ) // Project entity. In addition to the JSON properties as defined by the specification, it holds activities of the // project organized by title and by ID in a map. type Project struct { ID string `json:"id"` Title string `json:"title"` activitiesByTitle map[string]*Activity activitiesByID map[string]*Activity activitiesLoaded bool } // NewProject creates a new project instance. func NewProject() *Project { return &Project{ activitiesByID: make(map[string]*Activity), activitiesByTitle: make(map[string]*Activity), } } // GetProjects retrieves all stored project instances. It does not load related data like activities or times. func GetProjects() ([]*Project, error) { folder, folderErr := getProjectFolder() if folderErr != nil { return nil, folderErr } dirErr := os.MkdirAll(folder, 0o750) if dirErr != nil { return nil, dirErr } files, filesErr := os.ReadDir(folder) if filesErr != nil { return nil, filesErr } projects := []*Project{} for _, file := range files { project, fileErr := readProject(filepath.Join(folder, file.Name())) if fileErr != nil { return nil, fileErr } projects = append(projects, project) } return projects, nil } // GetProject retrieves a single project by its ID. It does not load related data like activities or times. func GetProject(id string) (*Project, error) { file, fileErr := getProjectFile(id) if fileErr != nil { return nil, fileErr } project, err := readProject(file) if err != nil { return nil, err } project.ID = id return project, nil } // SetProject persists a project. func SetProject(project *Project) error { uuid, uuidErr := newUUID() if uuidErr != nil { return uuidErr } project.ID = uuid file, fileErr := getProjectFile(uuid) if fileErr != nil { return fileErr } return writeProject(file, project) } // readProject reads a project from a file and converts it into an entity instance. func readProject(filename string) (*Project, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() project := NewProject() if err := json.NewDecoder(file).Decode(&project); err != nil { return nil, err } return project, nil } // writeProject writes a project instance to a file. func writeProject(filename string, project *Project) error { dirErr := os.MkdirAll(filepath.Dir(filename), 0o750) if dirErr != nil { return dirErr } file, fileErr := os.Create(filename) if fileErr != nil { return fileErr } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") jsonErr := encoder.Encode(project) if jsonErr != nil { return jsonErr } return nil } // getProjectFolder returns the folder to store project entity files in. func getProjectFolder() (string, error) { return getDataDir("projects") } // getProjectFile returns the file name for a project entity instance. func getProjectFile(name string) (string, error) { folder, err := getProjectFolder() if err != nil { return "", err } return filepath.Join(folder, name+".json"), nil }