Skip to content
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

[RCP-48] Add/Edit with Media #136

Open
darnjo opened this issue Apr 24, 2024 Discussed in #116 · 2 comments
Open

[RCP-48] Add/Edit with Media #136

darnjo opened this issue Apr 24, 2024 Discussed in #116 · 2 comments

Comments

@darnjo
Copy link
Member

darnjo commented Apr 24, 2024

Discussed in #116

Originally posted by grispin January 5, 2024
Here a the latest proposal for a Media Upload specification using the OData stream specification


Media Upload

This is a proposed solution to RESO media upload using the OData streams specification Section 4.5.11.

Data Representation

Data Structure

These are the required fields to represent the upload process for media

<EntityType Name="Media">
  
  <Property Name="MediaStatus" Type="Edm.String" />
  <Property Name="MediaStatusDescription" Type="Edm.String" />
  
</EntityType>

Status of the Media

The status of the media will be tracked on the Media resource. The MediaStatus field will show the state of the media.

  • Incomplete - the Media record that does not have a complete record (typically missing byte array)
  • Processing - the Media record is currently being processed by the backend
  • Complete - the Media record is complete and can be used with it's related resource
  • Deleted - the Media is no longer published with its related resources
  • Rejected - the Media has failed post processing for some reason. Details of the problem will be presented in the MediaStatusDescription field.

Many validation tests for media cannot be performed until the complete image is uploaded and this can take time to process. These processes are often asynchronous.

Some examples of Rejected reasons could be:

  • Media stream does not match MediaType (Sent a video for a photo Media record)
  • The internal image checksum failed
  • Image failed copyright ID check

Media Upload Process - Happy Path

The creation of the media resource will be done using as a standard OData update process. (Defined in RCP-10). The post should include all known metadata about the media with the exception of only the media byte stream.

There will be multiple responses shown for the requests as we will be illustrating both when the media is handled directly by the WebApi server and when the WebApi server is using external references.

Create Media Resource

Create the initial media record using the OData standard. This could be an expanded record on another resource as a secondary approach but the example will be with Media as a tier 1 Resource/Model.

REQUEST

POST https://api.my-webapi.io/Media
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation
{
 "Caption": "Ipsum Lorum",
 "Order": 1,
 "ImageType": "image/jpeg",
 
}

RESPONSE - External Media storage

HTTP/2 201 Created
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.my-webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api-my-webapi.io/image/not_available.jpg",
  "@odata.mediaEditLink": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-zyxwut",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Incomplete",
  "MediaStatusDescription": "Awaiting Byte Stream",
}

RESPONSE - Media through WebApi

When the MediaEditLink annotation is not provided, there is an implicit URL for the editMediaLink https://api.my-webapi.io/Media('12345')/$value provided by the Odata specification

HTTP/2 201 Created
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context":"https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id":"Media('12345')",
  "@odata.editLink":"https://api.my-webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api-my-webapi.io/image/not_available.jpg",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Incomplete",
  "MediaStatusDescription": "Awaiting Byte Stream",
}

The return from the POST will return the Media record with the object in an Incomplete status. The next step in the process is to provide the byte stream for the media object to the server at the provided endpoint. The client MUST use the odata.mediaEditLink if provided and use the implicit URL only if one is not provided as per the OData specification.

Upload the Media byte array

The byte stream upload is a simple HTTP POST transaction to the provided to the endpoint. If it is an request outside the WebApi domain, then all required data (authentication, etc.) must be provided in the mediaEditLink URL. If mediaEditLink is not in WebApi domain, then the established authentication (cookies, authentication headers, etc.) must continue to be provided.

REQUEST - External Target

POST https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token=12345-zyxwut
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 200 OK

REQUEST - WebApi Target

POST https://api.my-webapi.io/Media('12345')/$value
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 200 OK

Media post processing

The original media record will transition to Processing state while the server prepares the media for distribution downstream. The implementation will do all the work required for distribution before changing the state to Complete

If any processing fails, the media record will be have a status of Rejected and the reasoning for the Media submitter will be populated in the MediaStatusDescription field for actions to be taken.

Rules may be written against the MediaStatus field to handle the order of operations required to publish a listing. Eg. Media must be complete before it can be attached to a listing or Media attached to a listing must all be Complete before a listing can be on-market.

Successful Media Upload

The media record can be queried to confirm the media has successfully been processed.

REQUEST

GET https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE - Through External Resource

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://storage.my-webapi.io/media/12345.jpg",
  "@odata.mediaEditLink": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-zyxwut",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Complete",
  "MediaStatusDescription": "Processing Complete",
}

RESPONSE - Through WebApi

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api.webapi.io/Media('12345')/$value",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Complete",
  "MediaStatusDescription": "Processing Complete",
}

Media Upload Process error states

These are the cases where the media record will not reach the Complete state without additional actions.

Media Record created but no byte stream provided

This will leave the media record in a Incomplete state and the vendor can do culling/clean up as required. The client can requery the Media record for the URLs to attempt to re-use the Media record. Rules can be used to prevent the transition of the listing until valid media is provided if required.

REQUEST

GET https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api-my-webapi.io/image/not_available.jpg",
  "@odata.mediaEditLink": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-zyxwut",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Incomplete",
  "MediaStatusDescription": "Awaiting Byte Stream",
}

REQUEST

POST https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token=12345-zyxwut
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 200 OK

Error Uploading Media

If a client gets a HTTP 4xx error (except for 409 see Write-Once behaviour below) uploading media they validate their mediaEditLink URL and re-attempt the upload. After a couple of retries, they should consider the upload a failure and contact the server provider.

This can happen in normal operation if the mediaEditLink is a pre-signed URL for a storage provider and the authentication credentials have timed out. Requiring the Media record would get a updated URL with a fresh set of credentials.

If the server has timed out the Media record due to lack of completion (a byte stream was not uploaded within 30 days for example), the server will transition the MediaStatus to the Rejected state.

Refreshed Case

In this case, the client will get a new mediaEditLink URL as the authentication token was updated.

REQUEST

POST https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api-my-webapi.io/image/not_available.jpg",
  "@odata.mediaEditLink": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-take2",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Incomplete",
  "MediaStatusDescription": "Awaiting Byte Stream",
}

REQUEST

POST https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token=12345-take2
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 200 OK

Rejected Case

In the case of a Rejected media record, the client should replace the media record by adding a new record and deleting the old one.

REQUEST

GET https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Rejected",
  "MediaStatusDescription": "Media record has timed out",
}
POST https://api.my-webapi.io/Media('12345')/$value
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 403 Forbidden

Media uploaded but byte stream rejected

The media provided is not of the correct type or failed some other validation process after upload but before distribution occurred.

Resubmit Case

This is the case where the server allows the client to replace the byte stream of a media record.

REQUEST

GET https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context": "https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id": "Media('12345')",
  "@odata.editLink": "https://api.webapi.io/Media('12345')",
  "@odata.mediaReadLink": "https://api-my-webapi.io/image/not_available.jpg",
  "@odata.mediaEditLink": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-take2",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Rejected",
  "MediaStatusDescription": "Byte stream is not of type image/jpeg",
}

** REQUEST **

POST https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token=12345-take2
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 200 OK

Write Once Behaviour for Servers

Some implementations have a write-once behaviour for media where the client gets a single upload of the media byte stream. After the stream is received the byte stream becomes read-only. Replacement Media must be done by adding a new Media record and deleting the old one.

In this case, the server will not provide the mediaEditLink annotation and client will get a HTTP 409 Conflict response from the server.

Write-Once Case

The client will try the resubmit behaviour above but will receive the HTTP 409 response when attempting to send the byte stream at which point the client switch to the write-once behaviour, creating a new media record with byte stream and then deleting the old record. This approach is because the client does not know ahead of time if the server is write-once or not.

REQUEST

GET https://api.my-webapi.io/Media('12345')
OData-Version: 4.01
Content-Type: application/json
Accept: application/json
Prefer: return=representation

RESPONSE

HTTP/2 200
OData-Version: 4.01
EntityId: "12345"
Location: https://api.my-webapi.io/Media('12345')
Content-Length: 200
Content-Type: application/json
Preference-Applied: return=representation
{
  "@odata.context":"https://api.my-webapi.io/$metadata#Media/$entity",
  "@odata.id":"Media('12345')",
  "@odata.editLink":"https://api.webapi.io/Media('12345')",
  "@odata.etag": "W/\"aBcDeFgHiJkLmNoPqRsTuVwXyz\"",
  "MediaObjectID": "12345",
  "Caption": "Ipsum Lorum",
  "Order": 1,
  "ImageType": "image/jpeg",
  
  "MediaStatus": "Rejected",
  "MediaStatusDescription": "Byte stream is not of type image/jpeg",
}

REQUEST

POST https://api.my-webapi.io/Media('12345')/$value
Content-Type: image/jpeg

<<Byte stream of media object>>

RESPONSE

HTTP/2 409 Conflict
  • Creation new record is done using the above examples
  • Deletion of the old record uses the standard Add/Edit behaviour on the media record
@darnjo darnjo added the RCP label Apr 24, 2024
@darnjo
Copy link
Member Author

darnjo commented Apr 24, 2024

@grispin: Please make a PR in a branch called rcp-48-add-edit-with-media with a new file in proposals called add-edit-with-media.md.

@grispin
Copy link

grispin commented Apr 26, 2024

Pull Request Ref: #137

@darnjo darnjo changed the title RCP-48 - Add/Edit with Media RCP-48: Add/Edit with Media Jul 1, 2024
@darnjo darnjo changed the title RCP-48: Add/Edit with Media [RCP-48] Add/Edit with Media Jul 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants