Introduce support for grouping multiple sequential file uploads into a single box using the `X-Warpbox-Batch` header. This is particularly useful for ShareX multi-file selections, which are sent as separate back-to-back requests.
Additionally, this change:
- Updates the ShareX configuration template to opt-in to batching by default.
- Switches ShareX configuration placeholders to the modern `{json:...}` format.
- Adds `thumbnailUrl` to the upload response schema and documents its usage.
143 lines
5.8 KiB
Go
143 lines
5.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"warpbox.dev/backend/libs/helpers"
|
|
"warpbox.dev/backend/libs/web"
|
|
)
|
|
|
|
type apiDocsData struct {
|
|
BaseURL string
|
|
UploadURL string
|
|
HealthURL string
|
|
RequestSchemaURL string
|
|
ResponseSchemaURL string
|
|
ShareXExamplePath string
|
|
ShareXExampleURL string
|
|
ShareXDownloadURL string
|
|
ShareXFileFieldName string
|
|
ShareXGroupWindow string
|
|
}
|
|
|
|
func (a *App) APIDocs(w http.ResponseWriter, r *http.Request) {
|
|
a.renderPage(w, r, http.StatusOK, "api.html", web.PageData{
|
|
Title: "API documentation",
|
|
Description: "Curl and ShareX upload examples for Warpbox.",
|
|
Data: apiDocsData{
|
|
BaseURL: a.cfg.BaseURL,
|
|
UploadURL: a.cfg.BaseURL + "/api/v1/upload",
|
|
HealthURL: a.cfg.BaseURL + "/api/v1/health",
|
|
RequestSchemaURL: a.cfg.BaseURL + "/api/v1/schemas/upload-request.json",
|
|
ResponseSchemaURL: a.cfg.BaseURL + "/api/v1/schemas/upload-response.json",
|
|
ShareXExamplePath: "examples/sharex/warpbox-anonymous.sxcu",
|
|
ShareXExampleURL: a.cfg.BaseURL + "/api/v1/upload",
|
|
ShareXDownloadURL: a.cfg.BaseURL + "/api/v1/sharex/warpbox-anonymous.sxcu",
|
|
ShareXFileFieldName: "sharex",
|
|
ShareXGroupWindow: uploadGroupWindow.String(),
|
|
},
|
|
})
|
|
}
|
|
|
|
func (a *App) ShareXAnonymousConfig(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Disposition", `attachment; filename="warpbox-anonymous.sxcu"`)
|
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
|
"Version": "18.0.0",
|
|
"Name": "Warpbox Anonymous Upload",
|
|
"DestinationType": "ImageUploader, TextUploader, FileUploader",
|
|
"RequestMethod": "POST",
|
|
"RequestURL": a.cfg.BaseURL + "/api/v1/upload",
|
|
"Headers": map[string]string{
|
|
"Accept": "application/json",
|
|
// Group a multi-file selection (sent as back-to-back requests) into
|
|
// one box. Remove this header for one box per file.
|
|
uploadBatchHeader: "sharex",
|
|
},
|
|
"Body": "MultipartFormData",
|
|
"FileFormName": "sharex",
|
|
"URL": "{json:boxUrl}",
|
|
"ThumbnailURL": "{json:thumbnailUrl}",
|
|
"DeletionURL": "{json:deleteUrl}",
|
|
"ErrorMessage": "{json:error}",
|
|
})
|
|
}
|
|
|
|
func (a *App) UploadRequestSchema(w http.ResponseWriter, r *http.Request) {
|
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": a.cfg.BaseURL + "/api/v1/schemas/upload-request.json",
|
|
"title": "Warpbox anonymous upload request",
|
|
"description": "Multipart/form-data request accepted by POST /api/v1/upload. Send one or more files using either the file or sharex field.",
|
|
"type": "object",
|
|
"properties": map[string]any{
|
|
"file": map[string]any{
|
|
"description": "One or more uploaded files. Use this field for curl and browser-style clients.",
|
|
"type": "array",
|
|
"items": map[string]any{"type": "string", "format": "binary"},
|
|
},
|
|
"sharex": map[string]any{
|
|
"description": "One or more uploaded files. Use this field for ShareX custom uploader configs.",
|
|
"type": "array",
|
|
"items": map[string]any{"type": "string", "format": "binary"},
|
|
},
|
|
"max_days": map[string]any{
|
|
"description": "Optional number of days before the box expires. Defaults to 7.",
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
},
|
|
"max_downloads": map[string]any{
|
|
"description": "Optional maximum number of downloads before the box expires.",
|
|
"type": "integer",
|
|
"minimum": 1,
|
|
},
|
|
"password": map[string]any{
|
|
"description": "Optional box password.",
|
|
"type": "string",
|
|
},
|
|
"obfuscate_metadata": map[string]any{
|
|
"description": "Optional checkbox-style value. When set with a password, hides file names/counts until unlock.",
|
|
"type": "string",
|
|
"enum": []string{"on"},
|
|
},
|
|
},
|
|
"anyOf": []any{
|
|
map[string]any{"required": []string{"file"}},
|
|
map[string]any{"required": []string{"sharex"}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func (a *App) UploadResponseSchema(w http.ResponseWriter, r *http.Request) {
|
|
helpers.WriteJSON(w, http.StatusOK, map[string]any{
|
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
"$id": a.cfg.BaseURL + "/api/v1/schemas/upload-response.json",
|
|
"title": "Warpbox anonymous upload JSON response",
|
|
"description": "JSON response returned by POST /api/v1/upload when Accept: application/json is sent.",
|
|
"type": "object",
|
|
"required": []string{"boxId", "boxUrl", "zipUrl", "manageUrl", "deleteUrl", "expiresAt", "files"},
|
|
"properties": map[string]any{
|
|
"boxId": map[string]any{"type": "string"},
|
|
"boxUrl": map[string]any{"type": "string", "format": "uri"},
|
|
"zipUrl": map[string]any{"type": "string", "format": "uri"},
|
|
"thumbnailUrl": map[string]any{"type": "string", "format": "uri", "description": "Thumbnail of the most recently uploaded file (placeholder until generated)."},
|
|
"manageUrl": map[string]any{"type": "string", "format": "uri", "description": "Private bearer URL for managing/deleting this upload. Returned only at upload time."},
|
|
"deleteUrl": map[string]any{"type": "string", "format": "uri", "description": "Private bearer URL for deleting this upload (GET or POST). Returned only at upload time."},
|
|
"expiresAt": map[string]any{"type": "string", "format": "date-time"},
|
|
"files": map[string]any{
|
|
"type": "array",
|
|
"items": map[string]any{
|
|
"type": "object",
|
|
"required": []string{"id", "name", "size", "url"},
|
|
"properties": map[string]any{
|
|
"id": map[string]any{"type": "string"},
|
|
"name": map[string]any{"type": "string"},
|
|
"size": map[string]any{"type": "string"},
|
|
"url": map[string]any{"type": "string", "format": "uri"},
|
|
"thumbnailUrl": map[string]any{"type": "string", "format": "uri"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|