API post comments with attached files

Hey @Sandra, this took a while longer than I had hoped. Nevertheless, this is an example of attaching files to new comments.

I include below C# code to achieve this. I could post javascript afterwards though it has a few gotchas to work around that I mention later.

The first step is to set the server (host_url) and a personal access token (token), if we are not relying on a local installation using Speckle Manager to handle accounts.

string host_url = "HOST_SERVER";
string token = "ACCESS_TOKEN";

With those in place, we can create a SpeckleApiClient object, which will be used to interact with the server.

using Speckle.Core.Api;
using Speckle.Core.Credentials;

Account account = new Account();
account.serverInfo = new ServerInfo(){ url=host_url };
account.token = token;

var stream_id = "bb8470f5b6";
var commit_id = "34247c2a44";

var client = new Client(account);

In this example, I will attach a local file to my script and read it into a Stream and, from there, into a Blob object.

using System.IO;

var filePath = "./assets/blue.png";

byte[] blob;
using (var stream = new FileStream(filePath, FileMode.Open))
{
    using (var memoryStream = new MemoryStream())
    {
        stream.CopyTo(memoryStream);
        blob = memoryStream.ToArray();
    }
}

With that in place and an authenticated client, we can add that File Blob to the Stream with an effective POST request.

I’m posting this as C# code as it isn’t encumbered by web sandboxing. Suppose you are using javascript to do this to the speckle.xyz server, or any server on a different host to your javascript app, you will have to use a CORS proxy.

A POST request with mode no-cors will add the file to the server but will not allow you to receive the return object.

Also fine would be to use Postman or similar to upload the file.

The server endpoint is in the form of host_url/api/stream/{stream_id}/blob, and the request body is a FormData object with a file key and the File Blob as the value.

using System.Net.Http;
using System.Net.Http.Headers;

var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
fileContent.Headers.ContentType = 
    MediaTypeHeaderValue.Parse("application/octet-stream");

var formData = new MultipartFormDataContent();
formData.Add(fileContent, "file", Path.GetFileName(filePath));

System.Net.Http.HttpResponseMessage response;

using (var http_client = new HttpClient())
{
    http_client.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", token);
    response = await http_client.PostAsync(
        $"{host_url}/api/stream/{stream_id}/blob", formData);
}

The server will return a JSON object with the file resource information, which you can then use to add the file to a comment.

The response object is in the form:

{
    "uploadResults": [
        {
            "blobId": "9ec.....",
            "fileName": "blue.png",
            "uploadStatus": 1,
            "fileSize": 4760,
            "formKey": "file"
        }
    ]
}

Speckle includes a namespaced version of Newtonsoft.Json, so you can use that to parse the response, but it isn’t critical.

using Speckle.Newtonsoft.Json;

string blobId = null;

if (response.IsSuccessStatusCode)
{
    var responseContent = await response.Content.ReadAsStringAsync();
    dynamic fileUploadResponse = JsonConvert.DeserializeObject(responseContent);
    blobId = fileUploadResponse.uploadResults[0].blobId;
}

With that key blobId, you can add the file to a comment.

We have a few other posts here on the forum that discuss the GraphQL for comments, but the minimal viable query that is effective is:

using GraphQL;
using GraphQL.Client.Http;

var query = @"mutation CommentCreateInput(
    $streamId: String!,
    $blobIds: [String!]!,
    $commitId: String!
  ) { 

  commentCreate(
    input: {
      streamId: $streamId, 
      resources: [
        {
          resourceType: commit
          resourceId: $commitId, 
        }
      ], 
      blobIds: $blobIds
      screenshot: null
      text: {
         type: ""doc"", 
         content: [{type: ""paragraph"", content: [
            {type: ""text"", text: ""My Comment""}]}]}
      },
      data: { location: { x: 10, y: 7.775978194702619, z: 6.737419726085228 } }
    }
  )
}";

var variables = new
{
  streamId = stream_id,
  commitId = commit_id,
  blobIds = new[] { blobId }
};

var request = new GraphQLRequest { Query = query, Variables = variables };    

With the query and variable substitutions in place, we can use the Speckle GQLClient to send the query.

using System.Threading;
using System.Threading.Tasks;

await client.GQLClient
  .SendMutationAsync<LimitedUserData>(request, CancellationToken.None)
  .ConfigureAwait(false);

And that’s it!!

We are missing a few functions with the commenting API that would make replicating the functions available in the Speckle Frontend at speckle.xyz, but this is the gist of it.

Currently missing features:

  • The ability to add a screenshot of the comment-defined viewport
  • The ability to add a selection of objects to the comment

I think the answer is yes; you can add files to the comments. Some parts of this workflow have been in flux for a while, but we hear the need to do better with the documentation!!!

4 Likes