feat(photos): full EXIF metadata extraction, backfill and info panel

- backend: switch EXIF parser to dsoprea/go-exif/v3 (all recognised tags, numeric normalization); store in photos.exif jsonb (migration 000003); add storage.Get and cmd/backfill-exif to backfill existing photos from S3 originals
- frontend: info panel renders all EXIF attributes with RU labels, enum translation and units (1/N, f/N, mm, EV); service tags hidden
- viewer: remove make-cover action (cover managed from panel only), icon-only download
- openspec: sync lightroom-workspace-ui artifacts (EXIF, cover-from-panel) and add plan for configurable info attributes/labels stored in user profile
This commit is contained in:
iu
2026-10-10 20:31:34 +03:00
parent d8da885020
commit e1592549b2
20 changed files with 744 additions and 108 deletions
+17 -2
View File
@@ -122,8 +122,23 @@ func (c *Client) DeletePrefix(ctx context.Context, prefix string) error {
return c.Delete(ctx, keys...)
}
func (c *Client) PresignGet(ctx context.Context, key string) (string, error) {
out, err := c.presign.PresignGetObject(ctx, &s3.GetObjectInput{
func (c *Client) Get(ctx context.Context, key string) ([]byte, error) {
out, err := c.s3.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(c.bucket),
Key: aws.String(key),
})
if err != nil {
return nil, fmt.Errorf("get object %s: %w", key, err)
}
defer out.Body.Close()
data, err := io.ReadAll(out.Body)
if err != nil {
return nil, fmt.Errorf("read object %s: %w", key, err)
}
return data, nil
}
func (c *Client) PresignGet(ctx context.Context, key string) (string, error) { out, err := c.presign.PresignGetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(c.bucket),
Key: aws.String(key),
}, s3.WithPresignExpires(c.presignTTL))