RESOStandards / transport

RESO Transport Workgroup - Specifications and Change Proposals
https://transport.reso.org
Other
18 stars 15 forks source link

Create Add/Edit Media Spec #88

Closed darnjo closed 1 year ago

darnjo commented 1 year ago

Step 1

Post data to Media resource with all the metadata available for the image prior to uploading the base64 encoded image data.

Note: In this step there are two cases ... either the record that the Media is attached to is included in the initial creation of the Media metadata (ResourceName, ResourceRecordKey), or it isn't. If not, then that information needs to be provided at some point (after step 2).

Step 2

Once the Media record has been created, the client can upload the binary image data by obtaining the pre-signed URL attached to the Media item that was created. The pre-signed URLs will be generated as a result of step (1), and will have an expiration timestamp associated with them.

Media Statuses

We also need to define statuses in the Media Resource to reflect the various stages of processing.

If the record has been rejected for some reason, either processing failed or there was a compliance issue (for example), there will also be a StatusDescription to let clients know what failed.

grispin commented 1 year ago

More detailed notes and examples for review.

Media Upload using Pre-signed URLs

This is a proposed solution to media upload.

Data Representation

Data Structure

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

<EntityType Name="Media">
  ...
  <Property Name="PresignedUrl" Type="Edm.String" />
  <Property Name="PresignedUrlExpiry" Type="Edm.DateTimeOffset" />
  <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.

Some examples of Rejected reason could be:

Media Upload Process - Happy Path

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

If the media is a reference to external URL reference, the server should ingest the MediaURL field in the post. The PresignedUrl related fields will not be populated in these cases.

Create Media Resource

Create the initial media record using the oData standard. This could be an expanded record on another resource a secondary approach but the example will be with Media as in 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

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":"https://api.my-webapi.io/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": "Incomplete",
  "MediaStatsDescription": "Awaiting Byte Stream",
  "PresignedUrl": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-zyxwut",
  "PresignedUrlExpiry": "2023-08-01T13:00:00+00:00",
}

The return from the POST will return a pre-signed URL with an validity window for that URL.

Upload the Media byte array

Thie bytestrem upload will be a simple HTTP POST transaction to the provided to the endpoint with no other required data. No external fields, headers or payloads are required to post the media stream. All requirements for the POST must be part of the PresignedUrl

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

Media post processing

The original media record will transition to Processing state and blank the PresignedUrl field while the server prepares the media for distribute downstream. The implementation will do all 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 can be written against the MediaStatus field

Successful Media Upload

The media record can up queries to confirm the media has successfully been processed.

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":"https://api.my-webapi.io/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": "Complete",
  "MediaStatsDescription": "Processing Complete",
  "PresignedUrl": null,
  "PresignedUrlExpiry": null,
}

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 resource for the PresignedUrl 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

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":"https://api.my-webapi.io/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": "Incomplete",
  "MediaStatsDescription": "Awaiting Byte Stream",
  "PresignedUrl": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-zyxwut",
  "PresignedUrlExpiry": "2023-08-01T13:00:00+00:00",
}

PresignedUrl Expires

If a Media record's PresignedUrlExpiry time is reached, the server can invalidate the media record by blanking the PresignedUrl and PresignedUrlExpiry fields and setting the MediaStatus to Rejected or providing updated the Presigned* fields to re-use the Media record

Refreshed Case

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":"https://api.my-webapi.io/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": "Incomplete",
  "MediaStatsDescription": "Awaiting Byte Stream",
  "PresignedUrl": "https://storage.my-webapi.io/media/12345.jpg?authentication_token=my-one-time-use-auth-token-12345-abcdefg",
  "PresignedUrlExpiry": "2023-09-01T14:00:00+00:00",
}

Rejected Case

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":"https://api.my-webapi.io/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",
  "MediaStatsDescription": "Media record has timed out",
  "PresignedUrl": null,
  "PresignedUrlExpiry": null,
}

Media uploaded but byte stream rejected

If the media is rejected, the vendor can eithe allow for the record update by providing the Presigned* fields or leave them blanked to indicate the record has write-once behaviour. Rules can enforce the media records be corrected/deleted before state of the parent resource can be transitioned.

Resubmit Case

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":"https://api.my-webapi.io/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",
  "MediaStatsDescription": "Byte stream is not of type image/jpeg",
  "PresignedUrl": "https://storage.my-webapi.io/media/12345-attempt2.jpg?authentication_token=my-one-time-use-auth-token-12345-abcdefg",
  "PresignedUrlExpiry": "2023-09-01T14:00:00+00:00",
}

Write-Once Case

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":"https://api.my-webapi.io/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",
  "MediaStatsDescription": "Byte stream is not of type image/jpeg",
  "PresignedUrl": null,
  "PresignedUrlExpiry": null,
}
darnjo commented 1 year ago

Thanks for putting this together. I think we should talk about some of this a bit.