api: add comic metadata fields to API responses
Update API layer to expose all 14 new comic metadata fields in media item
responses for both list and detail endpoints.
Handler Changes (internal/handlers/media.go):
- Added encoding/json import for JSON unmarshaling
- Updated ListMediaItems() to include 15 new fields in JSON response
- Updated GetMediaItem() to include 15 new fields in JSON response
- Added numericToFloat() helper: Convert pgtype.Numeric to float64
- Added jsonBytesToMap() helper: Convert JSONB []byte to map[string]interface{}
- Field name mapping: WebUrl (not WebURL), proper pgtype handling
TypeScript Types (web/src/types/api.d.ts):
- Updated MediaItemSummary interface with 15 new optional fields
- manga_type: 'unknown' | 'no' | 'yes' | 'yes_and_right_to_left'
- reading_direction: 'auto' | 'ltr' | 'rtl' | 'vertical'
- Universal fields: series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating
- Comic-specific: story_arc, is_black_and_white, alternate_info (nested type), scan_information, summary
- Proper TypeScript typing with string literals for type safety
API Response Fields Added:
Reading Direction:
- manga_type: Raw Manga field from ComicInfo.xml
- reading_direction: Computed direction (auto/ltr/rtl/vertical)
Universal Metadata (all formats):
- series_count, volume, imprint, age_rating, web_url, metadata_notes, community_rating
Comic-Specific:
- story_arc, is_black_and_white, alternate_info (JSONB object), scan_information, summary
Part of Phase 5: API Layer Updates
Implementation: IMPLEMENTATION_PLAN_MERGE_METADATA_READING_DIRECTION.md
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"bookhoard/internal/services"
|
||||
"bookhoard/internal/utils"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
@@ -39,6 +40,21 @@ type CreateMediaItemRequest struct {
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors []string `json:"contributors"`
|
||||
// NEW: Reading direction and comic metadata fields
|
||||
MangaType string `json:"manga_type"` // 'unknown' | 'no' | 'yes' | 'yes_and_right_to_left'
|
||||
ReadingDirection string `json:"reading_direction"` // 'auto' | 'ltr' | 'rtl' | 'vertical'
|
||||
SeriesCount int32 `json:"series_count"`
|
||||
Volume int32 `json:"volume"`
|
||||
Imprint string `json:"imprint"`
|
||||
AgeRating string `json:"age_rating"` // 'Everyone' | 'Teen' | 'Mature' | 'Adult'
|
||||
WebURL string `json:"web_url"`
|
||||
MetadataNotes string `json:"metadata_notes"`
|
||||
CommunityRating float64 `json:"community_rating"`
|
||||
StoryArc string `json:"story_arc"`
|
||||
IsBlackAndWhite bool `json:"is_black_and_white"`
|
||||
AlternateInfo string `json:"alternate_info"` // JSON string
|
||||
ScanInformation string `json:"scan_information"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
// UpdateMediaItemRequest represents the request for updating a media item
|
||||
@@ -55,6 +71,21 @@ type UpdateMediaItemRequest struct {
|
||||
DatePublished string `json:"date_published"`
|
||||
Publisher string `json:"publisher"`
|
||||
Contributors []string `json:"contributors"`
|
||||
// NEW: Reading direction and comic metadata fields
|
||||
MangaType string `json:"manga_type"` // 'unknown' | 'no' | 'yes' | 'yes_and_right_to_left'
|
||||
ReadingDirection string `json:"reading_direction"` // 'auto' | 'ltr' | 'rtl' | 'vertical'
|
||||
SeriesCount int32 `json:"series_count"`
|
||||
Volume int32 `json:"volume"`
|
||||
Imprint string `json:"imprint"`
|
||||
AgeRating string `json:"age_rating"` // 'Everyone' | 'Teen' | 'Mature' | 'Adult'
|
||||
WebURL string `json:"web_url"`
|
||||
MetadataNotes string `json:"metadata_notes"`
|
||||
CommunityRating float64 `json:"community_rating"`
|
||||
StoryArc string `json:"story_arc"`
|
||||
IsBlackAndWhite bool `json:"is_black_and_white"`
|
||||
AlternateInfo string `json:"alternate_info"` // JSON string
|
||||
ScanInformation string `json:"scan_information"`
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
// CreateMediaNoteRequest represents the request for creating a media note
|
||||
@@ -660,6 +691,23 @@ func (mh *MediaHandler) ListMediaItems(c *echo.Context) error {
|
||||
"created_at": item.CreatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"updated_at": item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"format_group": item.FormatGroup, // already string
|
||||
// NEW: Reading direction fields
|
||||
"manga_type": textToString(item.MangaType),
|
||||
"reading_direction": textToString(item.ReadingDirection),
|
||||
// NEW: Universal metadata fields
|
||||
"series_count": item.SeriesCount,
|
||||
"volume": item.Volume,
|
||||
"imprint": textToString(item.Imprint),
|
||||
"age_rating": textToString(item.AgeRating),
|
||||
"web_url": textToString(item.WebUrl),
|
||||
"metadata_notes": textToString(item.MetadataNotes),
|
||||
"community_rating": numericToFloat(item.CommunityRating),
|
||||
// NEW: Comic-specific fields
|
||||
"story_arc": textToString(item.StoryArc),
|
||||
"is_black_and_white": item.IsBlackAndWhite,
|
||||
"alternate_info": jsonBytesToMap(item.AlternateInfo),
|
||||
"scan_information": textToString(item.ScanInformation),
|
||||
"summary": textToString(item.Summary),
|
||||
}
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"data": resolvedItems})
|
||||
@@ -707,6 +755,23 @@ func (mh *MediaHandler) GetMediaItem(c *echo.Context) error {
|
||||
"updated_at": item.UpdatedAt.Time.Format("2006-01-02T15:04:05Z07:00"),
|
||||
"format_group": item.FormatGroup,
|
||||
"format_mimetype": textToString(item.FormatMimetype),
|
||||
// NEW: Reading direction fields
|
||||
"manga_type": textToString(item.MangaType),
|
||||
"reading_direction": textToString(item.ReadingDirection),
|
||||
// NEW: Universal metadata fields
|
||||
"series_count": item.SeriesCount,
|
||||
"volume": item.Volume,
|
||||
"imprint": textToString(item.Imprint),
|
||||
"age_rating": textToString(item.AgeRating),
|
||||
"web_url": textToString(item.WebUrl),
|
||||
"metadata_notes": textToString(item.MetadataNotes),
|
||||
"community_rating": numericToFloat(item.CommunityRating),
|
||||
// NEW: Comic-specific fields
|
||||
"story_arc": textToString(item.StoryArc),
|
||||
"is_black_and_white": item.IsBlackAndWhite,
|
||||
"alternate_info": jsonBytesToMap(item.AlternateInfo),
|
||||
"scan_information": textToString(item.ScanInformation),
|
||||
"summary": textToString(item.Summary),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1577,3 +1642,26 @@ func (mh *MediaHandler) ServeFile(c *echo.Context) error {
|
||||
http.ServeFile(c.Response(), c.Request(), fullPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// numericToFloat converts pgtype.Numeric to float64, returning 0 if invalid
|
||||
func numericToFloat(n pgtype.Numeric) float64 {
|
||||
if n.Valid {
|
||||
f, err := n.Float64Value()
|
||||
if err == nil {
|
||||
return f.Float64
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// jsonBytesToMap converts []byte JSONB to map[string]interface{}, returning nil if invalid
|
||||
func jsonBytesToMap(b []byte) map[string]interface{} {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(b, &result); err != nil {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user