diff --git a/frontend/src/client/generated/sdk.gen.ts b/frontend/src/client/generated/sdk.gen.ts index dc8109089..9da6cb55c 100644 --- a/frontend/src/client/generated/sdk.gen.ts +++ b/frontend/src/client/generated/sdk.gen.ts @@ -2623,7 +2623,7 @@ export const taskAttachmentsDelete = (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 = (options: Options): RequestResult => (options.client ?? client).get({ security: [{ diff --git a/frontend/src/client/generated/types.gen.ts b/frontend/src/client/generated/types.gen.ts index c645683c2..62c4dad0d 100644 --- a/frontend/src/client/generated/types.gen.ts +++ b/frontend/src/client/generated/types.gen.ts @@ -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 */ diff --git a/pkg/routes/api/v2/task_attachments.go b/pkg/routes/api/v2/task_attachments.go index 47bda3688..4be85849d 100644 --- a/pkg/routes/api/v2/task_attachments.go +++ b/pkg/routes/api/v2/task_attachments.go @@ -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) diff --git a/pkg/webtests/huma_task_attachment_test.go b/pkg/webtests/huma_task_attachment_test.go index f2321e361..82599fd9d 100644 --- a/pkg/webtests/huma_task_attachment_test.go +++ b/pkg/webtests/huma_task_attachment_test.go @@ -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)