-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Richard/docs/images #36
Conversation
I've realized that we can prune on every new image upload
The card JSON sent over the network does not need to have a 1-to-1 correspondence to the database Card schema. If we don't want garbage images building up in MongoDB, we can simply embed the image data as a buffer inside each Card. When a Card gets erased in MongoDB, the image buffer goes along with it. In regards to the consideration for uploading ahead of time, when we are restricting image sizes to be under 1 MB, it will take mere seconds to upload. Now that I have given it more thought, it seems more dangerous to split image and card update requests, because there can be a race condition where either the request to upload the image arrives at the server or the request to create a card does, but not both, and there will be inconsistencies. Requiring all card update information to travel within a single request avoids this: either the entire request fails, or the entire request succeeds. The image get endpoint is still useful, because |
@shangzhel I don't think that's the case. First of all, an 1mb image taking mere seconds to upload might not always be a valid assumption, because internet speed such as 50kbps exist in places such as New Zealand. Secondly, if we were to enforce that each imagine has to be bounded to a card then yes, we will have inconsistency that'll break the requirements. However, this need not be the case. For that if everything is structured as I've proposed, a card update can not succeed without the imagine upload, because it won't have the imagine URL, which can only be obtained in the response by the server after the success of the image upload. And if the imagine upload succeeds without the card update, nothing will happen, the previous imagine, or lack there of will be retained and we'll just have one imagine on the server not bounded to any card, which will be pruned at the next imagine upload. |
But can you ensure that the image upload and card create/update operations are atomic?
But there can be a race condition, consider the following sequence of requests:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some adjustments needed to fit the conventions that the other endpoints use. Refer to the code comments.
docs/api/Card.ts
Outdated
@@ -9,6 +9,7 @@ export interface Card { | |||
email: string; | |||
jobTitle: string; | |||
company: string; | |||
imageUrl?: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of keeping an entire URL, it can keep just an ObjectId to the image in the other array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May I know which other array you are referring to?
@shangzhel
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The array no longer exist as now there's a 1 to 1 relationship between the image and the cards, I've modify the API accordingly
docs/api/images.md
Outdated
|
||
Response body: | ||
```ts | ||
import type { ObjectId } from './ObjectId'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ObjectId is unused here, no need to import it.
docs/api/images.md
Outdated
import type { ObjectId } from './ObjectId'; | ||
|
||
interface ImagePostResponse { | ||
imageUrl: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can be an Id instead of a URL.
docs/api/images.md
Outdated
} | ||
``` | ||
|
||
# /api/image/imageId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use the route parameter syntax.
@shangzhel This is a very valid scenario that I did not consider before. There are three way that I can think of to address this. First is to make image and card update a single request, as you've proposed. Second is to set a time stamp on images to say, prevent the image from getting pruned within a day. Third is that, if when attempting to update card, the server complain image not found. Default to another end point which upload the image and update the card on a single POST. Tell me what you think is best (maybe justify) and I'll just do that. Also regarding to your requested changes about /api/image/imageId, I thought we wanted to keep the route a get request so that we can simply feed the url into the src property in whatever image element on the frontend we'll use. |
When we were discussing image upload, I remembered that the binary data of the image would be stored on the database. Is it possible to generate a url for you to open the image with an external browser? What if we could turn binary data sent from the backend into a url to display on in the img element? If we let the image be transmitted as a binary buffer over the api, will our image validation library still be able to validate it? If so we can create an image validation middleware for endpoints #23 and #25. Patching a cards details would be an overwrite of its contents in this situation. Alternatively I've been reading on the transactions feature offered by Mongoose: https://mongoosejs.com/docs/transactions.html
1a. User already has a previous transaction when starting a new one
2a. User uploads image on front end
4a. Image has been uploaded from 2a.
*a. User closes page
*b. User cancels transaction
I'm slightly worried about Heroku's environment between the two api calls. How often does it clean out its deployment environment? If we have to consider more permanent transaction management solutions this may not be valid. |
We could defer this issue to Sprint 2 once we have at least the basic functions down. Then we can have a more focused attempt at it. When we have some front-end developed, we also have to identify how to insert the user's image files into the front end interface (allowing for binary encoding). |
Yes, and yes, that's exactly what I'm saying for /api/image/imageId, that would be the url we feed into the img element, or used to open the image in a new window (though have to be the same browser as we need to cookie for authentication). Yes, and very likely we will. One problem with the transaction is that, overtime we'll have unaborted transaction garbages on the server, not to mention that managing those transaction between all the different endpoint would be difficult to say the least as far as I can imagine. |
The more complicated or split APIs get the more difficult it is to get right. This is because REST APIs by design are stateless, which means we are not allowed to assume that any sequence of more than one requests actually completes. This is why I propose each API completes exactly one action.
I had forgotten about this, but it is not the best either way so I will adjust my code comment to propose a different change.
That's not how transactions work. MongoDB transactions are database-wide. This means once a transaction starts, no other transaction can begin, regardless of whether it is the same user or otherwise. If our server begins a transaction and the client disconnects, it becomes a denial-of-service attack while the server waits for the next message from the client. There is also no way to "store a transaction". They are not documents, transactions is a MongoDB function, not data. |
Fair enough, I will move forward with the first way. |
@shangzhel |
@waltervan00 now I'm thinking that I might have to take over #23 and #25. Or alternatively, I'll wait for you to finish writing, and then I'll start working on it, which might be a bit inefficient. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just minor changes left, then it should be good to go.
docs/api/Image.ts
Outdated
@@ -0,0 +1 @@ | |||
type Image = string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to export the type, and add a newline at the end of the file.
docs/api/card.md
Outdated
@@ -1,12 +1,16 @@ | |||
# /api/card | |||
## PUT | |||
- Authenticated | |||
- 413 Payload Too Large if image string is larger than 1 MB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verifying only the image string will require first processing the entire request body which can itself DoS the server. Instead we should limit the total request size for all requests. The server can do this with a middleware. Instead of noting this here, state this restriction in docs/api/README.md.
docs/api/card.md
Outdated
|
||
Request body: | ||
```ts | ||
import type { Card } from './Card'; | ||
|
||
type CardPatchRequest = Pick<Card, 'id'> & Partial<Omit<Card, 'id'>>; | ||
interface CardPatchRequest extends Pick<Card, 'id'> & Partial<Omit<Card, 'id' | 'hasImage'>> { | ||
image?: Image; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There needs to be a way to indicate the intention to "remove image". I think it will be best to assign null
to image
, so the type needs to be Image | null
instead of just Image
.
docs/api/images.md
Outdated
@@ -0,0 +1,8 @@ | |||
# /api/image/:cardId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either change the endpoint path to /api/images/:cardId
to match the file name or rename the file to image.md
.
docs/api/card.md
Outdated
@@ -34,12 +38,15 @@ Response body: None | |||
## PATCH | |||
- Authenticated | |||
- 410 Gone if card does not exist. | |||
- 413 Payload Too Large if image string is larger than 1 MB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, instead of noting this here, state this restriction for all requests in docs/api/README.md.
@chomosuke |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
I've decided to separate images upload from card update, considering that we might want to upload the image while user is still filling out other details. To avoid garbage images that isn't attach to any business card building up on the database and taking space, we can prune the images for a user every time that user uploads an image.
This means I'll also need to:
related to #30