Topics

Content Management API


In This Guide

This section describes how to use Brightspot’s Content Management API (CMA).


Hello Content Management API

Overview of the Content Management API

The GraphQL plugin includes a configurable Content Management API endpoint type. Without any additional development work, endpoints can be created in order to expose APIs to create, read, update, and delete existing Dari records. The high level of configurability allows for quick and simple proof of concept tests. This endpoint type is not intended for production use because endpoints defined via project code are more consistent and easier to maintain in different environments.

Configuring the Content Management API

  1. Log in to Brightspot and open the hamburger menu.
  2. Under the Admin cluster, select the APIs option.
  3. Create a new GraphQL Management API:
  • Name—used to differentiate API endpoints in the GraphQL CMS tool. It is also used to generate a default path for the API endpoint.
  • Path—appended to /graphql/management/ to create the path where the API endpoint is available.
  • Full Path (Read Only)—displays full path after applying logic for Path field.
  • Read Types—Dari record types used to generate the schema and expose query APIs.
  • Read/Write Types—Dari record types used to generate the schema and expose mutation APIs.
  • Default Query Limit—default limit applied to all Dari record queries for the API endpoint. This value can be overridden via a limit argument on any query field.
  • Max Query Limit—max limit of items returned from any Dari record query for the API endpoint. This limit is respected even with an overridden default limit via the limit argument on any query field.
  • CORS Configuration—configuration of allowed origins and headers for the API endpoint (see Cross-Origin Resource Sharing).
  • Persisted Query Protocol—option to select a specific persisted query protocol implementation for the API endpoint (see Persisted queries).
  • Schema Locale—locale used during GraphQL schema documentation generation.
  • Paths (Read Only)—all the paths the API endpoint is available at.
  • Clients (Read Only)—API clients that have access to the API endpoint.

Click Save in order to expose the endpoint.


Creating and updating records in Brightspot Content Management API

This guide will explain how to create and update Brightspot records via CMA GraphQL mutation APIs.

Prerequisites for using the CMA

For endpoint configuration, see Custom Content Management API development and Hello Content Management API.

Introduction to the CMA

In the Content Management API, value differences for the proposed state are used to mutate an object. Passing in state differences, rather than an entire state object, accurately represents how users input data into Brightspot.

Background to the CMA

Consider the following record classes where setters and getters are omitted for brevity:

public class Foo extends Record {

    private String title;

    private Integer number;

    private List<Integer> numbers;

    private boolean flag;

    @Recordable.Embedded
    private Bar bar;

    private Baz baz;

}
@Recordable.Embedded
public class Bar extends Record {

   private String barTitle;
}
public class Baz extends Record {

   private String bazTitle;
}

Following is a portion of the schema, concerned with mutation types and based on the aforementioned record classes, with Foo configured as a mutable entry field:

type Mutation {
    com_company_FooSave(diffs: [com_company_FooDiffInput], id: DiffId, toolUser: RefId): com_company_Foo
}

input com_company_FooDiffInput {
    com_company_BarDiff: com_company_BarInput
    com_company_FooDiff: com_company_FooInput
    id: DiffId
}

input com_company_FooInput {
    bar: DiffId
    baz: RefId
    flag: Boolean
    number: Int
    numbers: [Int]
    title: String
}

input com_company_BarInput {
    barTitle: String
}

Inputs:

  • id: diff ID to match the main diff input for the record creation or update
  • diffs: every embedded record updated by the mutation requires a diff input with its relevant changes
  • toolUser: user ID, username, or email so history for the record can be updated appropriately

DiffId inputs can be an existing UUID, new UUID, or even a random identifier. Existing UUIDs are necessary for updating existing records while new UUIDs create new records. The random identifier feature allows callers to create new records with invalid UUIDs, such as “1,” to simplify record creation. Random identifiers are matched up and converted to new, valid UUIDs upon record creation.

RefId inputs are utilized for pointing to non-embedded records via their ID.

CMA examples

Below are a few examples of typical create and update CMA save mutations. They are run against a schema with Foo and Baz configured as mutable entry fields. Since Baz is not an embedded record on Foo, and instead referenced by Foo, it needs to be an entry field in order to retrieve more data than _id and _type.

Record creation

The following example shows the creation of a Foo record. Notice that a non-embedded Baz record, already in existence, is assigned to it.

Query
mutation MyMutation {
    com_company_FooSave(id: "1", toolUser: "jentile@brightspot.com",
        diffs: [
            {
                id: "1",
                com_company_FooDiff: {
                    title: "Hello World!"
                    flag: true
                    number: 1
                    numbers: [2, 3]
                    baz: "00000177-72e6-d6fd-a97f-76f6002a0000"
                }
            }
        ]
    ) {
        _id
        title
        flag
        number
        numbers
        baz {
            _id
            bazTitle
        }
    }
}
Response
{
  "data": {
    "com_company_FooSave": {
      "_id": "00000177-72ea-d6fd-a97f-76fa15700000",
      "title": "Hello World!",
      "flag": true,
      "number": 1,
      "numbers": [2, 3],
      "baz": {
        "_id": "00000177-72e6-d6fd-a97f-76f6002a0000",
        "bazTitle": "Hello Human!"
      }
    }
  }
}

Here’s an example where embedded record data is supplied during creation. An embedded Bar record is created and assigned to the new Foo record.

Query
mutation MyMutation {
    com_company_FooSave(id: "1", toolUser: "jentile@brightspot.com",
        diffs: [
            {
                id: "1",
                com_company_FooDiff: {
                    bar: "2"
                }
            },
            {
                id: "2",
                com_company_BarDiff: {
                    barTitle: "Hello Atom!"
                }
            }
        ]
    ) {
        _id
        bar {
            _id
            barTitle
        }
    }
}
Response
{
  "data": {
    "com_company_FooSave": {
      "_id": "00000177-72ea-d6fd-a97f-76fa15700000",
      "bar": {
        "_id": "00000177-72ea-d6fd-a97f-76fa15700001",
        "barTitle": "Hello Atom!"
      }
    }
  }
}

Record update

The following example shows an update to the existing Foo record from the previous example. Notice that the embedded Bar record does not need to be reassigned to the Foo record, yet its value can still be updated. Additionally, a different non-embedded Baz record is assigned.

Query
mutation MyMutation {
    com_company_FooSave(id: "00000177-72ea-d6fd-a97f-76fa15700000", toolUser: "jentile@brightspot.com",
        diffs: [
            {
                id: "00000177-72ea-d6fd-a97f-76fa15700000",
                com_company_FooDiff: {
                    title: "Hello Universe!"
                    baz: "00000177-72f2-d6fd-a97f-76f281400000"
                }
            },
            {
                id: "00000177-72ea-d6fd-a97f-76fa15700001",
                com_company_BarDiff: {
                    barTitle: "Hello Quark!"
                }
            }
        ]
    ) {
        _id
        title
        bar {
            _id
            barTitle
        }
        baz {
            _id
            bazTitle
        }
    }
}
Response
{
  "data": {
    "com_company_FooSave": {
      "_id": "00000177-72ea-d6fd-a97f-76fa15700000",
      "title": "Hello Universe!",
      "bar": {
        "_id": "00000177-72ea-d6fd-a97f-76fa15700001",
        "barTitle": "Hello Quark!"
      },
      "baz": {
        "_id": "00000177-72f2-d6fd-a97f-76f281400000",
        "bazTitle": "Hello Sentient Being!"
      }
    }
  }
}


Uploading files in Brightspot Content Management API

This guide will explain how to upload files to Brightspot records via CMA GraphQL mutation APIs.

Prerequisites for uploading a file

For endpoint configuration, see Custom Content Management API development and Hello Content Management API.

Introduction to uploading a file

Being that the official GraphQL spec does not currently support upload mutations, the CMA implements a popular community extension to the specification: GraphQL multipart request specification.

Background to uploading a file

Consider the following Image content class that will store our file and be configured as a mutable entry field on our endpoint:

package com.company;

import com.psddev.cms.db.Content;
import com.psddev.dari.util.StorageItem;

public class Image extends Content {

    private StorageItem file;

    public StorageItem getFile() {
        return file;
    }

    public void setFile(StorageItem file) {
        this.file = file;
    }
}

Example of uploading a file

<!DOCTYPE>
<html lang="en">
    <head>
        <title>CMA StorageItem Upload Example</title>
    </head>
    <body>
        <input type="file" id="file-selector">
        <script>
            (() => {
                const fileSelector = document.getElementById('file-selector');
                fileSelector.addEventListener('change', (event) => {
                    
                    const file = event.target.files[0];
                    const query = 'mutation MyMutation($file: Upload!) { com_company_ImageSave( diffs: [{ ' +
                        'com_company_ImageDiff: { file: { file: $file } } }] ) { file {  path  } }}'
                    const variables = { file: null }
                    const operations = { query, variables }
                    const map = { 0: ['variables.file'] }

                    const formData = new FormData();
                    formData.append('operations', JSON.stringify(operations))
                    formData.append('map', JSON.stringify(map))
                    formData.append('0', file, file.name)

                    fetch('<API_ENDPOINT_URL>', {
                        method: 'POST',
                        headers: {
                            'x-api-key': '<API_KEY>'
                        },
                        body: formData,
                    })
                    .then(response => response.json())
                    .then(responseData => {
                        console.log('Success:', responseData);
                        document.body.innerHTML = '<div>Upload Successful!</div>'
                    })
                    .catch((error) => {
                        console.error('Error:', error);
                    });
                });
            })();
        </script>
    </body>
</html>


Custom Content Management API development

Overview of custom CMA development

For stable production functionality, it typically makes more sense to build a custom Content Management endpoint via code, rather than entirely through editorial configuration. An endpoint class can ensure that a fixed set of APIs are exposed with configuration that should not change between different environments.

Implementing custom CMAs

Create a new Java class extending ContentManagementApiEndpoint.Implement required methods:getPathSuffix—appended to /graphql/management/ to create the path where the API endpoint is available.getEntryFields—objects wrapping Dari record types used to generate the schema and expose APIs.

Example:

public class FooContentManagementApiEndpoint extends ContentManagementApiEndpoint {

    @Override
    protected String getPathSuffix() {
        return "foo"; // API endpoint is available at path '/graphql/management/foo'
    }

    @Override
    public List<ContentManagementEntryPointField> getEntryFields() {
        return Arrays.asList(
               new ContentManagementEntryPointField(Foo.class, true),
               new ContentManagementEntryPointField(Bar.class, true));
    }
}