package backend import ( "errors" "io/fs" "os" "path/filepath" "strings" ) // CharacterService serves character images from a single directory. type CharacterService struct { root *os.Root } // NewCharacterService opens the characters directory. Files outside it can never be served. func NewCharacterService(path string) (*CharacterService, error) { root, err := os.OpenRoot(path) if err != nil { return nil, err } return &CharacterService{root: root}, nil } // List returns the file names of all character images. func (c *CharacterService) List() ([]string, error) { entries, err := fs.ReadDir(c.root.FS(), ".") if err != nil { return nil, err } characters := []string{} for _, entry := range entries { if !entry.IsDir() && isImage(entry.Name()) { characters = append(characters, entry.Name()) } } return characters, nil } // Open opens the character image with the given file name. func (c *CharacterService) Open(name string) (*os.File, error) { if name == "" || name != filepath.Base(name) || !isImage(name) { return nil, ErrInvalidName } f, err := c.root.Open(name) if errors.Is(err, fs.ErrNotExist) { return nil, ErrNotFound } return f, err } // Close releases the characters directory. func (c *CharacterService) Close() error { return c.root.Close() } func isImage(name string) bool { switch strings.ToLower(filepath.Ext(name)) { case ".jpg", ".jpeg", ".png": return true } return false }