feat(API): media library
This commit is contained in:
1 parent
95ae043d3e
commit
0d99544579
65 files changed
+3992
-1530
No files matched your search
@@ -1,99 +1,47 @@
|
||||
Feature: Uploading an image to an article
|
||||
Feature: POST /v1/article/{id}/image is a thin alias that creates a library image
|
||||
|
||||
As an authenticated RenewCMS user managing the CMS
|
||||
I want to attach an image file to an existing article and get back the
|
||||
image's public location
|
||||
So that an unknown article answers 404 the same way every other article
|
||||
write does, without ever creating an orphaned image, and a disallowed
|
||||
content type is rejected with 400 before it becomes part of the
|
||||
article, without a maintainer needing to trust the client to send a
|
||||
sane file
|
||||
As the current editor, until the editor slice moves it to /v1/media
|
||||
I want to keep uploading through the article route and get {id, location} back
|
||||
So that the editor keeps working while images are library assets
|
||||
|
||||
Background:
|
||||
Given internal/infrastructure/useCases.CreateImageUseCase.CreateImage
|
||||
already checks the uploaded content type against the allowed list
|
||||
(image/png, image/jpeg, image/webp, image/svg+xml) and the size
|
||||
against the 10485760-byte maximum before calling image.Repository.Create,
|
||||
returning image.ErrImageTypeNotAllowed or image.ErrImageTooLarge and
|
||||
calling the repository not at all when either check fails
|
||||
And internal/infrastructure/persistence's ArticleRepository.AddImage
|
||||
already returns article.ErrArticleNotFound when the article id doesn't
|
||||
match any row, and image.ErrImageNotFound when the article exists but
|
||||
the image id doesn't, both asserted with errors.Is
|
||||
And before this slice, api/handlers/image.Handler.PostImage existed and
|
||||
was wired to CreateImageUseCase and UpdateArticleUseCase.AddImage, but
|
||||
no route called it, and every error it got back from either use case
|
||||
fell through to a blanket 500
|
||||
And this slice mounts POST /v1/article/{id}/image in api/router.go, in
|
||||
the same jwtauth Verifier + Authenticator group as the other article
|
||||
write routes, and changes the upload flow so that the article's
|
||||
existence is confirmed before the image is created, so that an
|
||||
unknown article id never leaves an orphaned image behind
|
||||
And this slice changes PostImage's error handling so that errors.Is(err,
|
||||
image.ErrImageTypeNotAllowed) or errors.Is(err, image.ErrImageTooLarge)
|
||||
answers 400 with the sentinel's own message, errors.Is(err,
|
||||
article.ErrArticleNotFound) answers 404 with body
|
||||
apperr.ArticleNotFoundErrorMessage, and any other error still falls
|
||||
back to a generic 500
|
||||
And PostImage reads the uploaded file from the multipart form field
|
||||
"file"
|
||||
And on success PostImage answers 201 with a JSON body
|
||||
{"id": ..., "location": ...} holding the created image's numeric id
|
||||
and public path, e.g. "/static/uploadedImages/<stored-filename>"
|
||||
And this slice (features/remove_article_image.feature) adds the "id"
|
||||
field to that body; the id lets a client later address the image
|
||||
through DELETE /v1/article/{id}/image/{imageId}
|
||||
Given the route stays in the article router's jwtauth group with RequireCurrentToken and
|
||||
RequireVerifiedUser, and is marked for removal in the editor slice
|
||||
And the handler calls GetArticleUseCase first, so an unknown article is 404
|
||||
apperr.ArticleNotFoundErrorMessage and nothing is created
|
||||
And it then calls the same CreateImageUseCase as POST /v1/media/, with UploadedByID from the
|
||||
jwt user_id claim, attaches nothing to the article, and answers 201 {"id", "location"}
|
||||
where location is the image's url
|
||||
And type, size and unreadable errors map as on /v1/media/: 415, 413, 400
|
||||
And DELETE /v1/article/{id}/image/{imageId} is not mounted
|
||||
|
||||
Scenario: Uploading an allowed image type to an existing article succeeds
|
||||
Given a valid jwt cookie for a user with id 1
|
||||
And the article repository contains an article with id 1 titled
|
||||
"Backyard Renovation Update"
|
||||
When the client sends POST /v1/article/1/image with that cookie and a
|
||||
multipart form field "file" holding a file named "before.png" with
|
||||
content type "image/png"
|
||||
Scenario: Uploading through the alias creates a library image
|
||||
Given a valid jwt cookie for user 1 and article 1 "Backyard Renovation Update"
|
||||
When the client sends POST /v1/article/1/image with a real PNG named "before.png"
|
||||
Then the response status is 201
|
||||
And the response body is JSON with a "location" field whose value is
|
||||
"/static/uploadedImages/before.png"
|
||||
And the response body's "id" field equals the new image's id in the
|
||||
image repository
|
||||
And the image repository contains a new image whose Path is
|
||||
"/static/uploadedImages/before.png"
|
||||
And the article repository's article with id 1 has that new image's id
|
||||
in its Images slice
|
||||
And the body has "id" equal to the new image's id and "location" equal to its url
|
||||
And GET /v1/media/ lists the image with uploaded_by of user 1
|
||||
And article 1 is unchanged
|
||||
|
||||
Scenario: Uploading to an unknown article answers 404 and creates no image
|
||||
Given a valid jwt cookie for a user with id 1
|
||||
And the article repository has no article with id 9999
|
||||
When the client sends POST /v1/article/9999/image with that cookie and a
|
||||
multipart form field "file" holding a file named "before.png" with
|
||||
content type "image/png"
|
||||
Then the response status is 404
|
||||
And the response body is the text "Article not found"
|
||||
Scenario: An unknown article is 404 and creates no image
|
||||
When the client sends POST /v1/article/9999/image with a valid PNG
|
||||
Then the response status is 404 and the body is "Article not found"
|
||||
And the image repository's Create is never called
|
||||
And no image exists in the image repository
|
||||
|
||||
Scenario: Uploading a disallowed content type answers 400 before the article is touched
|
||||
Given a valid jwt cookie for a user with id 1
|
||||
And the article repository contains an article with id 1 titled
|
||||
"Backyard Renovation Update"
|
||||
When the client sends POST /v1/article/1/image with that cookie and a
|
||||
multipart form field "file" holding a file named "notes.pdf" with
|
||||
content type "application/pdf"
|
||||
Then the response status is 400
|
||||
And the response body is the text
|
||||
"the file must be a PNG, JPEG, WEBP, or SVG image"
|
||||
And the image repository's Create is never called
|
||||
And the article repository's article with id 1 still has an empty
|
||||
Images slice
|
||||
Scenario Outline: Rejected files use the media status codes
|
||||
When the client sends POST /v1/article/1/image with <file>
|
||||
Then the response status is <status> and Create is never called
|
||||
|
||||
Scenario: Uploading an image still requires authentication
|
||||
Given no jwt cookie is sent
|
||||
And the article repository contains an article with id 1 titled
|
||||
"Backyard Renovation Update"
|
||||
When the client sends POST /v1/article/1/image without a cookie and a
|
||||
multipart form field "file" holding a file named "before.png" with
|
||||
content type "image/png"
|
||||
Then the response status is 401
|
||||
And the image repository's Create is never called
|
||||
And the article repository's article with id 1 still has an empty
|
||||
Images slice
|
||||
Examples:
|
||||
| file | status |
|
||||
| "notes.pdf" as application/pdf | 415 |
|
||||
| a valid PNG padded to 10485761 bytes | 413 |
|
||||
| "broken.png" as image/png with text bytes | 400 |
|
||||
|
||||
Scenario: The alias still requires authentication
|
||||
When the client sends POST /v1/article/1/image with no cookie
|
||||
Then the response status is 401 and Create is never called
|
||||
|
||||
Scenario: The removed delete route is gone
|
||||
When the client sends DELETE /v1/article/1/image/10 with a valid cookie
|
||||
Then the response status is 405 and image 10 still exists
|
||||
Reference in new issue
Block a user