From c27def3167bfdd5dbc8bb9a3d5ddde5b3cd47198 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 15 Mar 2018 12:46:02 -0600 Subject: [PATCH] Asset Manager Tutorial --- docs/_config.yml | 2 + docs/source/integrating/asset-management.md | 314 ++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 docs/source/integrating/asset-management.md diff --git a/docs/_config.yml b/docs/_config.yml index fe7146988..2a6fa2725 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -100,6 +100,8 @@ sidebar: children: - title: Authentication url: /integrating/authentication/ + - title: Asset Managment + url: /integrating/asset-management/ - title: Configuring the Comment Stream url: /integrating/configuring-comment-stream/ - title: Configuring the Admin diff --git a/docs/source/integrating/asset-management.md b/docs/source/integrating/asset-management.md new file mode 100644 index 000000000..a4888f4c5 --- /dev/null +++ b/docs/source/integrating/asset-management.md @@ -0,0 +1,314 @@ +--- +title: Asset Management +permalink: /integrating/asset-management/ +--- + +One of the most frequent questions that we get asked by organizations trying to +integrate Talk is: _How do we hook our CMS up to Talk so that articles are in +sync?_ + +This guide is designed to explain the steps to take your base installation of +Talk and configure it to allow only assets pushed into it from your CMS, and +keep your URL/title in sync. We won't cover here how to install the plugin, as +it is covered in our [Plugins Overview](/talk/plugins/). + +## Why do we need to create a plugin? + +By default, Talk will use "Lazy Asset Creation" to dynamically generate Assets +in Talk in order to make it easier for lighter installations. In order to have +more strict control over this flow, we will create a plugin that will: + +1. Disable "Lazy Asset Creation" by [Overriding a Resolver](#Overriding-a-Resolver). +2. Create Assets from our CMS by [Creating a New Asset Route](#Creating-a-New-Asset-Route). +3. Facilitate updates from our CMS to keep Talk in sync by [Creating an Asset Update Route](#Creating-an-Asset-Update-Route). + +We will then modify our embed so that we can [Target the Asset](#Target-the-Asset). + +But first we should grab our basic plugin structure: + +```sh +# clone our example repo (that comes with all the code below!) +git clone https://github.com/coralproject/talk-plugin-asset-manager-example.git + +# checkout the step-1 tag that starts us off with the basic file structure of +# the plugin. +git checkout step-1 +``` + +## Overriding a Resolver + +First we'll replace the content of the `resolver.js` file with the following: + +```js +// We'll need to modify the behavior of how assets are +// "resolved" in Talk, so we override the base asset resolver +// for the RootQuery type. +module.exports = { + RootQuery: { + asset: async (root, args, ctx) => { + // We'll grab the id of the asset being requested + // such that we'll be able to lookup the asset. + const { id } = args; + if (!id) { + // If the ID isn't provided, we don't want to do + // anything. + return null; + } + + // A mouthful for sure, but we need to use the loader + // that is available on the graph context in order to + // lookup the asset by ID. + const asset = await ctx.loaders.Assets.getByID.load(id); + if (!asset) { + // If the asset can't be found, we don't want to do + // anything. + return null; + } + + // Send the asset back. + return asset; + }, + }, +}; +``` + +This serves to override the default asset resolver. You can of course, override +any other field in the schema to perform whatever action your business needs +require, including adding additional resolvers! You can refer to our +[GraphQL API Docs](/talk/reference/graphql/) to see what other fields you can +override. + +Without this, Talk will continue to use the "Lazy Asset Creation" to handle +resolving the `asset` edge, which is what we want to change. + +_Note, you can also get to this point by running `git checkout step-2`!_ + +## Creating a New Asset Route + +In order to create Assets now, we have to get our CMS to push those into Talk, +the easiest way to do this is by creating a custom route. We won't cover +specific CMS integrations, but will assume that there is some type of webhook +system you are able to utilize that will trigger when a new article is created. + +We'll replace the contents of the `router.js` file with the following: + +```js +// This file we'll create routes that will facilitate asset creation and +// updates. + +const authz = require('middleware/authorization'); + +module.exports = router => { + // We'll respond to a POST request on the following route where the request + // must have a valid ADMIN access token. + router.post( + '/api/v1/plugin/asset-manager-example', + authz.needed('ADMIN'), + async (req, res, next) => { + // Get the graph context from the request. + const { context } = req; + + // Grab from the graph context, the AssetModel that we can use to create + // the new Asset. Lots of object destructuring here, but this lets us keep + // the important business logic cleaner. + const { connectors: { models: { Assets } } } = context; + + try { + // Now we can create the asset that was passed to us in the body of the + // request as JSON. Check the schema of the Asset model by looking at: + // https://github.com/coralproject/talk/blob/master/models/asset.js + await Assets.create(req.body); + + // Let your webhook callback know we got it! + return res.status(204).end(); + } catch (err) { + return next(err); + } + } + ); +}; +``` + +This request handler when mounted on Talk will allow your CMS to send a POST +request to `${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example` with the +Asset as a JSON payload. In order to protect the endpoint from abuse, we add the +authorization middleware. This middleware essentially says, _you must be an +admin to hit this route_. We need to generate a token that can be used by your +CMS using the Talk cli tool: + +```sh +# find or create an admin user that can be used as the basis for the token +./bin/cli users list + +# create a token for the user with the given id +./bin/cli token create ${USER_ID} cms-token +``` + +You can attach the generated token to the request a few ways: + +1. HTTP Header: + + curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example \ + -XPOST \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + --data "${ASSET_JSON}" + +2. Query Parameter: + + curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example?access_token=${TOKEN} + -XPOST \ + -H "Content-Type: application/json" \ + --data "${ASSET_JSON}" + +Where `${ASSET_JSON}` is the JSON for your Asset matching the +[AssetSchema](https://github.com/coralproject/talk/blob/master/models/asset.js). + +_Note, you can also get to this point by running `git checkout step-3`!_ + +## Creating an Asset Update Route + +Now imagine the situation where you decide that you want to change the url slug +of the page, or update the title, now Talk is out of sync! Let's fix that. + +Update your `router.js` to the following: + +```js +// This file we'll create routes that will facilitate asset creation and +// updates. + +const authz = require('middleware/authorization'); + +module.exports = router => { + // We'll respond to a POST request on the following route where the request + // must have a valid ADMIN access token. + router.post( + '/api/v1/plugin/asset-manager-example', + authz.needed('ADMIN'), + async (req, res, next) => { + // Get the graph context from the request. + const { context } = req; + + // Grab from the graph context, the AssetModel that we can use to create + // the new Asset. Lots of object destructuring here, but this lets us keep + // the important business logic cleaner. + const { connectors: { models: { Assets } } } = context; + + try { + // Now we can create the asset that was passed to us in the body of the + // request as JSON. Check the schema of the Asset model by looking at: + // https://github.com/coralproject/talk/blob/master/models/asset.js + await Assets.create(req.body); + + // Let your webhook callback know we got it! + return res.status(204).end(); + } catch (err) { + return next(err); + } + } + ); + + // We'll respond to a PUT request on the following route where the request + // must also have a valid ADMIN access token. + router.put( + '/api/v1/plugin/asset-manager-example/:id', + authz.needed('ADMIN'), + async (req, res, next) => { + // Get the graph context from the request. + const { context } = req; + + // Grab from the graph context, the AssetModel that we can use to update + // the Asset. Lots of object destructuring here, but this lets us keep + // the important business logic cleaner. + const { connectors: { models: { Assets } } } = context; + + try { + // Now we can lookup the asset we're updating and apply out updates to + // the model atomically. + const asset = await Assets.findOneAndUpdate( + { id: req.params.id }, + req.body, + { + // We want to validate the model being updated. + runValidators: true, + } + ); + if (!asset) { + // The asset indicated by the ID wasn't found, let the webhook know! + return res.status(404).end(); + } + + // Let your webhook callback know we got it! + return res.status(204).end(); + } catch (err) { + return next(err); + } + } + ); +}; +``` + +As you can see from the previous step of [Creating a New Asset Route](#Creating-a-New-Asset-Route) +, we have added the new `PUT` route to the router. This is a simple addition +that allows your CMS to call into Talk when the asset has updated it's title, +it's url (or really anything in the [AssetSchema](https://github.com/coralproject/talk/blob/master/models/asset.js)) to keep the Talk Admin and links up to date. + +Following the previous example, you can issue the request as follows: + +```sh +curl ${TALK_ROOT_URL}/api/v1/plugin/asset-manager-example/${ASSET_ID} \ + -XPUT \ + -H "Authorization: Bearer ${TOKEN}" \ + -H "Content-Type: application/json" \ + --data "${ASSET_JSON}" +``` + +The difference from the previous curl example, is that this one changes the +method from a `POST` to a `PUT`, and we add the `${ASSET_ID}` to the end of the +url. + +_Note, you can also get to this point by running `git checkout step-4`!_ + +## Target the Asset + +Now that we have a way to create and update Assets, we now need a way to +reference it. One of the most important fields in the Asset model, is the `id`. +This `id` can be one generated from your CMS, or some other system, but must +be kept consistent. + +When you install Talk, and visit the admin panel, we can see under +`/admin/configure` in the tab for Tech Settings, an embed snippet: + +```html +
+ +``` + +We'll modify this to the following: + +```html +
+ +``` + +Adding the `asset_id` parameter to the render function will accomplish a very +important task. It will provide Talk with the specific ID of the asset to +associate with the displayed page. This is important because even if you update +the URL in the future, the embed will still reference the correct Asset. The +`${ASSET_ID}` should be replaced by your CMS with the correct Asset id using +your desired scripting/templating tools. + +At this point, you should have a fully built Talk plugin that can be paired with +some work on your CMS to create a fully integrated asset management pipeline! + +To view the fully completed source code, visit +https://github.com/coralproject/talk-plugin-asset-manager-example. \ No newline at end of file