Improve Immich-backed photos endpoints with robust mapping/error handling, full albums CRUD, reliable list pagination/sorting/filtering, and shared Nextcloud quota checks before upload.
130 lines
3.7 KiB
Go
130 lines
3.7 KiB
Go
package photos
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
type BulkAssetResult struct {
|
|
ID string `json:"id"`
|
|
Success bool `json:"success"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type createAlbumPayload struct {
|
|
AlbumName string `json:"albumName"`
|
|
Description string `json:"description,omitempty"`
|
|
AssetIDs []string `json:"assetIds,omitempty"`
|
|
}
|
|
|
|
type updateAlbumPayload struct {
|
|
AlbumName *string `json:"albumName,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
}
|
|
|
|
type bulkIDsPayload struct {
|
|
IDs []string `json:"ids"`
|
|
}
|
|
|
|
func (c *Client) GetAlbum(ctx context.Context, apiKey, albumID string) (Album, error) {
|
|
url := fmt.Sprintf("%s/albums/%s?withoutAssets=true", c.baseURL, albumID)
|
|
var album Album
|
|
if err := c.doJSON(ctx, http.MethodGet, url, apiKey, nil, &album, "get album", http.StatusOK); err != nil {
|
|
return Album{}, err
|
|
}
|
|
return album, nil
|
|
}
|
|
|
|
func (c *Client) CreateAlbum(ctx context.Context, apiKey, name, description string, assetIDs []string) (Album, error) {
|
|
payload := createAlbumPayload{
|
|
AlbumName: name,
|
|
Description: description,
|
|
AssetIDs: assetIDs,
|
|
}
|
|
var album Album
|
|
if err := c.doJSON(ctx, http.MethodPost, c.baseURL+"/albums", apiKey, payload, &album, "create album", http.StatusCreated); err != nil {
|
|
return Album{}, err
|
|
}
|
|
return album, nil
|
|
}
|
|
|
|
func (c *Client) UpdateAlbum(ctx context.Context, apiKey, albumID string, name, description *string) (Album, error) {
|
|
payload := updateAlbumPayload{
|
|
AlbumName: name,
|
|
Description: description,
|
|
}
|
|
url := fmt.Sprintf("%s/albums/%s", c.baseURL, albumID)
|
|
var album Album
|
|
if err := c.doJSON(ctx, http.MethodPatch, url, apiKey, payload, &album, "update album", http.StatusOK); err != nil {
|
|
return Album{}, err
|
|
}
|
|
return album, nil
|
|
}
|
|
|
|
func (c *Client) DeleteAlbum(ctx context.Context, apiKey, albumID string) error {
|
|
url := fmt.Sprintf("%s/albums/%s", c.baseURL, albumID)
|
|
return c.doJSON(ctx, http.MethodDelete, url, apiKey, nil, nil, "delete album", http.StatusOK, http.StatusNoContent)
|
|
}
|
|
|
|
func (c *Client) AddAlbumAssets(ctx context.Context, apiKey, albumID string, assetIDs []string) ([]BulkAssetResult, error) {
|
|
url := fmt.Sprintf("%s/albums/%s/assets", c.baseURL, albumID)
|
|
var results []BulkAssetResult
|
|
if err := c.doJSON(ctx, http.MethodPut, url, apiKey, bulkIDsPayload{IDs: assetIDs}, &results, "add album assets", http.StatusOK); err != nil {
|
|
return nil, err
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (c *Client) RemoveAlbumAssets(ctx context.Context, apiKey, albumID string, assetIDs []string) ([]BulkAssetResult, error) {
|
|
url := fmt.Sprintf("%s/albums/%s/assets", c.baseURL, albumID)
|
|
var results []BulkAssetResult
|
|
if err := c.doJSON(ctx, http.MethodDelete, url, apiKey, bulkIDsPayload{IDs: assetIDs}, &results, "remove album assets", http.StatusOK); err != nil {
|
|
return nil, err
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (c *Client) doJSON(ctx context.Context, method, url, apiKey string, reqBody any, dest any, operation string, okStatuses ...int) error {
|
|
var body io.Reader
|
|
if reqBody != nil {
|
|
data, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
body = bytes.NewReader(data)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("x-api-key", apiKey)
|
|
if reqBody != nil {
|
|
req.Header.Set("Content-Type", "application/json")
|
|
}
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
for _, status := range okStatuses {
|
|
if resp.StatusCode == status {
|
|
if dest != nil {
|
|
if err := json.NewDecoder(resp.Body).Decode(dest); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
io.Copy(io.Discard, resp.Body)
|
|
return &HTTPStatusError{Operation: operation, StatusCode: resp.StatusCode}
|
|
}
|