package store import ( "fmt" "os" "path/filepath" "strings" "time" "github.com/google/uuid" "printer.backend/internal/model" "printer.backend/internal/svgtemplate" ) // ItemStore manages items (SVG + metadata) in the template directory. type ItemStore struct { dir string } // NewItemStore creates a store that scans templateDir for *.meta.json files. func NewItemStore(templateDir string) *ItemStore { if templateDir == "" { templateDir = svgtemplate.OutputDir } return &ItemStore{dir: templateDir} } // List returns all items sorted by creation time (newest first). func (s *ItemStore) List() ([]model.Item, error) { return listFromDir(s.dir, func(name string) bool { return strings.HasSuffix(name, ".meta.json") }, "template dir", func(it model.Item) time.Time { return it.CreatedAt }, ) } // Get returns an item by ID. func (s *ItemStore) Get(id string) (model.Item, error) { list, err := s.List() if err != nil { return model.Item{}, err } item, err := findByID(list, id, func(it model.Item) string { return it.ID }) if err != nil { return model.Item{}, fmt.Errorf("item %w", err) } return item, nil } // Create generates SVG + metadata for a new item. func (s *ItemStore) Create(name string, spec model.ItemSpec) (model.Item, error) { if spec.SizeMM <= 0 { return model.Item{}, fmt.Errorf("size_mm must be positive") } basename := svgBasename(name) data := svgtemplate.Build(spec.SizeMM, spec.BleedMM, spec.MarginMM, spec.PaddingMM) if err := svgtemplate.WriteFile(basename, data); err != nil { return model.Item{}, fmt.Errorf("write svg: %w", err) } displayName := name if displayName == "" { displayName = strings.TrimSuffix(basename, filepath.Ext(basename)) } return svgtemplate.WriteMeta(basename, spec, displayName) } // Update regenerates the SVG and metadata for an existing item (preserves id, svg filename, created_at). func (s *ItemStore) Update(id string, name string, spec model.ItemSpec) (model.Item, error) { if spec.SizeMM <= 0 { return model.Item{}, fmt.Errorf("size_mm must be positive") } item, err := s.Get(id) if err != nil { return model.Item{}, err } data := svgtemplate.Build(spec.SizeMM, spec.BleedMM, spec.MarginMM, spec.PaddingMM) if err := svgtemplate.WriteFile(item.SVGTemplate, data); err != nil { return model.Item{}, fmt.Errorf("write svg: %w", err) } if name != "" { item.Name = name } item.Spec = spec metaPath := filepath.Join(s.dir, model.MetaFilename(item.SVGTemplate)) if err := writeJSON(metaPath, item); err != nil { return model.Item{}, fmt.Errorf("write meta: %w", err) } return item, nil } // Delete removes an item's SVG and metadata by ID. func (s *ItemStore) Delete(id string) error { item, err := s.Get(id) if err != nil { return err } for _, path := range []string{ filepath.Join(s.dir, item.SVGTemplate), filepath.Join(s.dir, model.MetaFilename(item.SVGTemplate)), } { if err := os.Remove(path); err != nil && !os.IsNotExist(err) { return err } } return nil } // SVGPath returns the path to an item's SVG file. func (s *ItemStore) SVGPath(id string) (string, error) { item, err := s.Get(id) if err != nil { return "", err } path := filepath.Join(s.dir, item.SVGTemplate) if _, err := os.Stat(path); err != nil { return "", fmt.Errorf("svg file missing: %w", err) } return path, nil } func svgBasename(name string) string { base := strings.TrimSpace(name) if base == "" { return uuid.NewString() + ".svg" } base = strings.ReplaceAll(base, " ", "_") if !strings.HasSuffix(strings.ToLower(base), ".svg") { base += ".svg" } return filepath.Base(base) }