summaryrefslogtreecommitdiff
path: root/internal/service
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-30 12:45:18 +0300
committerPaul Buetow <paul@buetow.org>2026-04-30 12:45:18 +0300
commit54bc9013aa0463962a1cfd67fa278de3d401d85a (patch)
tree526c719285a6153fdc82f8a7feb7b4ef98c1ce78 /internal/service
parent38e318e71d50e4340ddbf1ae63b3d85e958b4644 (diff)
ja: implement public share landing page and improve error mapping
- Add web/share.html with a minimal HTML5 video player, theme variables, centered layout, play overlay, and Back to Home link. - Update handleSharePage to return HTML for browser Accept headers (text/html or empty) and JSON for application/json. Return 410 Gone for expired shares. - Update handleShareStream to map service errors to distinct HTTP codes: ErrShareExpired -> 410, ErrShareNotFound/ErrMediaNotFound -> 404. - Improve ValidateShareToken and StreamSharedMedia to return sentinel errors (ErrShareNotFound, ErrShareExpired, ErrMediaNotFound) instead of generic string errors. - Update and add tests for share page HTML/JSON negotiation and share stream 404/410 responses.
Diffstat (limited to 'internal/service')
-rw-r--r--internal/service/media.go18
-rw-r--r--internal/service/media_test.go9
-rw-r--r--internal/service/no_rows_test.go8
3 files changed, 19 insertions, 16 deletions
diff --git a/internal/service/media.go b/internal/service/media.go
index f950db3..6f4b2db 100644
--- a/internal/service/media.go
+++ b/internal/service/media.go
@@ -35,8 +35,11 @@ func NewMediaService(store repository.MediaServiceStore, clk clock.Clock, mediaR
// Sentinel errors returned by the media service layer.
var (
- ErrNotFound = errors.New("not found")
- ErrForbidden = errors.New("access denied")
+ ErrNotFound = errors.New("not found")
+ ErrForbidden = errors.New("access denied")
+ ErrShareNotFound = errors.New("share not found")
+ ErrShareExpired = errors.New("share expired")
+ ErrMediaNotFound = errors.New("media not found")
)
func (s *mediaService) ListSets(ctx context.Context, userID int64) ([]model.Set, error) {
@@ -510,16 +513,16 @@ func (s *mediaService) ValidateShareToken(ctx context.Context, token string) (*m
return nil, fmt.Errorf("get share: %w", err)
}
if share == nil {
- return nil, nil
+ return nil, ErrShareNotFound
}
now := s.clock.Now()
if now.After(share.ExpiresAt) {
- return nil, nil
+ return nil, ErrShareExpired
}
if share.MaxUses != nil && share.UsedCount >= *share.MaxUses {
- return nil, nil
+ return nil, ErrShareExpired
}
return share, nil
@@ -530,16 +533,13 @@ func (s *mediaService) StreamSharedMedia(ctx context.Context, token string) (*Fi
if err != nil {
return nil, err
}
- if share == nil {
- return nil, errors.New("invalid or expired share")
- }
media, err := s.store.GetMediaByID(ctx, share.MediaID)
if err != nil {
return nil, fmt.Errorf("get media: %w", err)
}
if media == nil {
- return nil, errors.New("media not found")
+ return nil, ErrMediaNotFound
}
_ = s.store.UseShare(ctx, token)
diff --git a/internal/service/media_test.go b/internal/service/media_test.go
index 1ab7dfb..33912b4 100644
--- a/internal/service/media_test.go
+++ b/internal/service/media_test.go
@@ -905,14 +905,17 @@ func TestMediaService_ValidateShareToken(t *testing.T) {
}
svc := NewMediaService(store, newMockClock(), "/tmp/media")
res, err := svc.ValidateShareToken(ctx, "abc")
- if err != nil {
- t.Fatalf("unexpected error: %v", err)
- }
if tt.wantValid {
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
if res == nil {
t.Fatal("expected valid share")
}
} else {
+ if err == nil {
+ t.Fatal("expected error for invalid share")
+ }
if res != nil {
t.Fatal("expected nil share")
}
diff --git a/internal/service/no_rows_test.go b/internal/service/no_rows_test.go
index 3606b63..9200668 100644
--- a/internal/service/no_rows_test.go
+++ b/internal/service/no_rows_test.go
@@ -135,8 +135,8 @@ func TestService_NoRows_ReturnsNil(t *testing.T) {
}
svc := NewMediaService(store, newMockClock(), "/tmp/media")
sh, err := svc.ValidateShareToken(ctx, "nope")
- if err != nil {
- t.Fatalf("expected no error, got %v", err)
+ if !errors.Is(err, ErrShareNotFound) {
+ t.Fatalf("expected ErrShareNotFound, got %v", err)
}
if sh != nil {
t.Fatalf("expected nil share, got %+v", sh)
@@ -153,8 +153,8 @@ func TestService_NoRows_ReturnsNil(t *testing.T) {
}
svc := NewMediaService(store, newMockClock(), "/tmp/media")
_, err := svc.StreamSharedMedia(ctx, "nope")
- if err == nil {
- t.Fatal("expected error for missing share")
+ if !errors.Is(err, ErrShareNotFound) {
+ t.Fatalf("expected ErrShareNotFound, got %v", err)
}
})