mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-11-09 00:22:38 -06:00
feat(attachments)!: always return the original from the v2 download route
Address review comment: previews moved to their own route, so the download route no longer takes preview_size.
BREAKING CHANGE: the v2 attachment download route ignores preview_size. Request previews from GET /tasks/{task}/attachments/{attachment}/preview?size= instead.
This commit is contained in:
@@ -2623,7 +2623,7 @@ export const taskAttachmentsDelete = <ThrowOnError extends boolean = true>(optio
|
||||
/**
|
||||
* Download a task attachment
|
||||
*
|
||||
* Returns the raw bytes of one attachment. Requires read access to the task. Pass preview_size to get a downscaled preview instead, as a JPEG or, when the image has transparency, as a PNG. When no preview can be made of the attachment (not an image, undecodable, or too large) the request fails with 422 and code 4038 instead of returning the original. The Content-Type header carries the mime type of the bytes that are returned.
|
||||
* Returns the raw bytes of one attachment. Requires read access to the task. For a downscaled image, use the preview endpoint.
|
||||
*/
|
||||
export const taskAttachmentsDownload = <ThrowOnError extends boolean = true>(options: Options<TaskAttachmentsDownloadData, ThrowOnError>): RequestResult<TaskAttachmentsDownloadResponses, TaskAttachmentsDownloadErrors, ThrowOnError> => (options.client ?? client).get<TaskAttachmentsDownloadResponses, TaskAttachmentsDownloadErrors, ThrowOnError>({
|
||||
security: [{
|
||||
|
||||
@@ -9288,20 +9288,11 @@ export type TaskAttachmentsDownloadData = {
|
||||
*/
|
||||
attachment: number;
|
||||
};
|
||||
query?: {
|
||||
/**
|
||||
* If set, return a downscaled preview instead of the original: sm=100px, md=200px, lg=400px, xl=800px. The preview is a JPEG, or a PNG when the image has transparency. Fails with 422 for attachments that have no preview, like non-images.
|
||||
*/
|
||||
preview_size?: 'sm' | 'md' | 'lg' | 'xl';
|
||||
};
|
||||
query?: never;
|
||||
url: '/tasks/{task}/attachments/{attachment}';
|
||||
};
|
||||
|
||||
export type TaskAttachmentsDownloadErrors = {
|
||||
/**
|
||||
* preview_size was set but the attachment has no preview (code 4038), or preview_size is invalid.
|
||||
*/
|
||||
422: VikunjaErrorModel;
|
||||
/**
|
||||
* Error
|
||||
*/
|
||||
|
||||
@@ -80,7 +80,7 @@ func RegisterTaskAttachmentRoutes(api huma.API) {
|
||||
Register(api, huma.Operation{
|
||||
OperationID: "task-attachments-download",
|
||||
Summary: "Download a task attachment",
|
||||
Description: "Returns the raw bytes of one attachment. Requires read access to the task. Pass preview_size to get a downscaled preview instead, as a JPEG or, when the image has transparency, as a PNG. When no preview can be made of the attachment (not an image, undecodable, or too large) the request fails with 422 and code 4038 instead of returning the original. The Content-Type header carries the mime type of the bytes that are returned.",
|
||||
Description: "Returns the raw bytes of one attachment. Requires read access to the task. For a downscaled image, use the preview endpoint.",
|
||||
Method: http.MethodGet,
|
||||
Path: "/tasks/{task}/attachments/{attachment}",
|
||||
Tags: tags,
|
||||
@@ -96,10 +96,6 @@ func RegisterTaskAttachmentRoutes(api huma.API) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"422": {
|
||||
Description: "preview_size was set but the attachment has no preview (code 4038), or preview_size is invalid.",
|
||||
Content: defaultErrorResponse(api).Content,
|
||||
},
|
||||
},
|
||||
}, taskAttachmentsDownload)
|
||||
|
||||
@@ -196,25 +192,14 @@ func taskAttachmentsUpload(ctx context.Context, in *taskAttachmentUploadInput) (
|
||||
// taskAttachmentsDownload exists because no handler.Do* fits a file body; bytes
|
||||
// stream from the StreamResponse callback without buffering — attachments can be large.
|
||||
func taskAttachmentsDownload(ctx context.Context, in *struct {
|
||||
TaskID int64 `path:"task" doc:"The id of the task the attachment belongs to."`
|
||||
AttachmentID int64 `path:"attachment" doc:"The id of the attachment to download."`
|
||||
PreviewSize string `query:"preview_size" enum:"sm,md,lg,xl" doc:"If set, return a downscaled preview instead of the original: sm=100px, md=200px, lg=400px, xl=800px. The preview is a JPEG, or a PNG when the image has transparency. Fails with 422 for attachments that have no preview, like non-images."`
|
||||
TaskID int64 `path:"task" doc:"The id of the task the attachment belongs to."`
|
||||
AttachmentID int64 `path:"attachment" doc:"The id of the attachment to download."`
|
||||
}) (*huma.StreamResponse, error) {
|
||||
a, err := authFromCtx(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if in.PreviewSize != "" {
|
||||
preview, err := models.GetTaskAttachmentPreview(ctx, a, in.TaskID, in.AttachmentID, models.GetPreviewSizeFromString(in.PreviewSize))
|
||||
if err != nil {
|
||||
return nil, translateDomainError(err)
|
||||
}
|
||||
return &huma.StreamResponse{Body: func(hctx huma.Context) {
|
||||
webfiles.WriteAttachmentPreview((*humaecho.Unwrap(hctx)).Response(), preview)
|
||||
}}, nil
|
||||
}
|
||||
|
||||
file, err := models.GetTaskAttachmentForDownload(a, in.TaskID, in.AttachmentID)
|
||||
if err != nil {
|
||||
return nil, translateDomainError(err)
|
||||
|
||||
@@ -164,6 +164,21 @@ func TestTaskAttachmentsV2(t *testing.T) {
|
||||
assert.Equal(t, "nosniff", rec.Header().Get("X-Content-Type-Options"))
|
||||
})
|
||||
|
||||
t.Run("Download returns the original even when a preview size is passed", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
require.NoError(t, err)
|
||||
token := humaTokenFor(t, &testuser1)
|
||||
|
||||
var content bytes.Buffer
|
||||
require.NoError(t, png.Encode(&content, image.NewNRGBA(image.Rect(0, 0, 400, 300))))
|
||||
id := uploadOneAttachment(t, e, token, "image.png", content.Bytes())
|
||||
|
||||
rec := humaRequest(t, e, http.MethodGet, "/api/v2/tasks/1/attachments/"+strconv.FormatInt(id, 10)+"?preview_size=md", "", token, "")
|
||||
require.Equal(t, http.StatusOK, rec.Code, "body: %s", rec.Body.String())
|
||||
assert.Equal(t, content.Bytes(), rec.Body.Bytes())
|
||||
assert.Contains(t, rec.Header().Get("Content-Disposition"), "image.png")
|
||||
})
|
||||
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
e, err := setupTestEnv()
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user