Search code examples
javascriptfetch-apiopenproject

OpenProject API docs - Attachment endpoint - how to get this to work OR better understanding of multipart/form-data


This is from: https://www.openproject.org/docs/api/endpoints/attachments/

To add an attachment to a work package, a client needs to issue a request of type multipart/form-data with exactly two parts.

The first part must be called metadata. Its content type is expected to be application/json, the body must be a single JSON object, containing at least the fileName and optionally the attachments description.

The second part must be called file, its content type should match the mime type of the file. The body must be the raw content of the file. Note that a filename must be indicated in the Content-Disposition of this part, however it will be ignored. Instead the fileName inside the JSON of the metadata part will be used.

I believe I'm understanding a general idea of what is occurring, i.e. how multipart/form data works as an enctype, why it exists, I get that I need to send two parts in the above description. One a JSON Object, and the other a file that also has a content disposition set with a filename parameter...

Beyond this very loose "understanding" I couldn't/wouldn't be able to translate this to a fetch statement or doing something in Postman (when I've tried it's summarily failed).

Could someone point me in the right direction to assist in my endeavor?

(note, I generally use Fetch/javascript for any such activities, if that matters...)

My last, not working, code:

const formData = new FormData();
    let uploadFile = articleImages[0].src;
    let jsonPayload = { "metadata": { "fileName": "randomfilename.jpg", "description": "Random Description" }};
    console.log(jsonPayload);
    formData.append( "metdata", jsonPayload );
    formData.append( "file", uploadFile);
    for (const value of formData.values()) {
      console.log(value);
    }
    fetch('https://***/api/v3/work_packages/***/attachments', {
      method: 'POST',
      headers: {
        'Authorization': 'Basic ' + btoa('apikey:***'),
        'referrer': '',
        'credentials': 'include',
        'mode':'cors'
      },
      body: formData,
    })

This would give the error:

{_type: 'Error', errorIdentifier: 'urn:openproject-org:api:v3:errors:PropertyConstraintViolation', message: "File can't be blank.", _embedded: {…}}
errorIdentifier: "urn:openproject-org:api:v3:errors:PropertyConstraintViolation"
message: "File can't be blank."
_embedded: {details: {…}}
_type: "Error"
[[Prototype]]: Object

Thank you all.

To answer recent comments:

  1. In postman:

    "_type": "Error", "errorIdentifier": "urn:openproject-org:api:v3:errors:MultipleErrors", "message": "Multiple field constraints have been violated.", "_embedded": { "errors": [ { "_type": "Error", "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation", "message": "File can't be blank.", "_embedded": { "details": { "attribute": "filename" } } }, { "_type": "Error", "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation", "message": "The content type of the file cannot be blank.", "_embedded": { "details": { "attribute": "contentType" } } } ] }

See image for what I sent: enter image description here


Solution

  • Ahoi rogelio,

    first thing is, that your metadata JSON is nested too much. Value should be { "fileName": "randomfilename.jpg", "description": "Random Description" }, without the metadata key on top.

    Second, I do not really know, how fetch constructs its requests, but this is an example of how the request body should look like:

    -----------------------------35500998851541290908403510937
    Content-Disposition: form-data; name="metadata"
    
    {"fileName":"test.txt"}
    -----------------------------35500998851541290908403510937
    Content-Disposition: form-data; name="file"; filename="test.txt"
    Content-Type: text/plain
    
    This is a test text.
    
    -----------------------------35500998851541290908403510937--
    

    In addition, it would be interesting to know, what is in the embedded details of the response error. ;)

    Hope I can help!

    Edit

    I built this request in Postman, and it works without flaws. This is how to setup the request: postman setup