The spec is now built from the same calls that register the routes, so it
can't drift from the router like the swag comments did (all /api/v1
routes were missing).
- /api/v1 endpoints are typed Huma operations; the spec is OpenAPI 3.1 at
/openapi.json (3.0 at /openapi-3.0.json, YAML too), docs at /docs.
/openapi and /swagger/* redirect there
- Bearer token security is declared per operation; token creation and
the new GET /api/v1/health are public
- Legacy routes use the same handlers, are hidden from the spec and keep
the Deprecation header
- /sync/reset is hidden (debug only); the /sync/new, /sync/quick and
/sync/new/full aliases are removed (use /sync and /sync/full)
- Invalid parameters return 422 (validation); every error is
{"error": "..."}; empty lists are [] instead of null
- /sync/progress is documented as progress or result (oneOf)
- Tests validate the spec and check real responses of every documented
operation against it (kin-openapi)
- Removed swag annotations, cmd/docs, echo-swagger and the swag tool
64 lines
1.4 KiB
Go
64 lines
1.4 KiB
Go
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
|
|
}
|