# Introduction

These documents are intended for development teams working for Memento Yearbook Publishers looking to integrate Memento Yearbook with their own in-house applications.

Currently, we provide two tools to assist with integrations:

  • Webhooks
    • These are used to notify your server about events that happen with your Memento Yearbook account, such as yearbooks being submitted or cancelled.
  • A publisher API
    • A RESTful API to perform additional actions (typically in response to webhook events), such as cancelling a yearbook that was submitted to you, or creating schools, books or team members programmatically.

# Recipes

# Being notified when a book/cover is submitted/cancelled

Setup a webhook endpoint and listen for the proof.submitted and proof.cancelled events (fired for a book or cover).

By storing the id of the book, the school_id, the type of the proof (book/cover) and the proof_id, you have enough information to internally track which yearbooks are submitted/cancelled.

# Downloading the PDF for a book/cover when it's submitted

Setup a webhook endpoint and listen for the proof.submitted event. This event is typically sent twice: once for the book and once for the cover. Each event contains a downloadable link to the respective PDF (i.e. if this is a cover proof, the PDF will be for the cover).

This link is only valid for 3 months, so be sure to download the PDF to your own servers.

# Providing my own proofing workflow to my schools

In the Customization Dashboard (opens new window), set the Proofing Workflow to Basic in the Proofing tab. This disables the built in review and approval workflow for yearbooks, replacing it with a one-click submit to publisher button.

Next, setup a webhook endpoint and listen for the proof.submitted event. This event is fired when the school clicks submit to publisher and will contain a downloadable link to the PDF. This event is typically sent twice: once for the book and once for the cover.

Finally, download the PDF to your own server and use it to provide your own proofing workflow. If you need to send a link to the yearbook's editor-in-chief, use the book_editor_in_chief_email property in the proofing event.

Synchronizing proofs which are cancelled

When the proof.submitted webhook event is fired, Memento Yearbook's internal proof status will be APPROVED. A book or cover that is APPROVED cannot be edited by the school or publisher.

If your proofing workflow allows yourself or the school to reject a proof, you'll want to synchronize this event with Memento Yearbook so the school can make their changes.

There are two ways to cancel a proof:

# Automating the setup of schools, yearbooks and team users

You can programmatically automate the setup of a school, yearbook and team users using a series of sequential API calls.

Before you do this, you need to setup at least one book type (e.g. 8.5 x 11 Softcover). To do this, use our GUI in the Book Product Dashboard (opens new window).

The pseudo-code for this flow is:

  1. List your book types and find the book type id you want to use for book.
  2. Check if the school id exists
  3. Create a school
  4. Create a book for the school by supplying both the book type id and school id created in steps #1 and #3.
  5. (Optionally) Create additional team members by supplying the book id in step #4.

# Code example

The full code for this node.js example can be found in our Bitbucket Code Sample Repo (opens new window).

TIP

This example assumes you are creating a new school/book and team users.

If you are creating a new book for an existing school, use the Get School API to get the school ID instead.

If you are creating new team members for an existing book, use the Get School API to get the school and book id instead.

const axios = require('axios');
const { get } = require('lodash');
const moment = require('moment');

// #todo - update this token with the one generated from your publisher dashboard
const token = 'z2rnvHqIbBy4Xicfr2bZZARVzv3qrQXYXSsevdDw';
const client = axios.create({
  baseURL: 'https://yearbooks.me/api/external/publishers/v1',
  headers: {
    Authorization: `Bearer ${token}`,
    Accept: 'application/json',
  },
});

/**
 * Gets publisher book types.
 * @returns {Promise<any>}
 */
async function getBookTypes() {
  const response = await client.get(`book_types`);
  return response.data || response;
}

/**
 * Creates a new school.
 * @param inObjSchoolParams
 * @returns {Promise<any>}
 */
async function createSchool(inObjSchoolParams) {
  const response = await client.post(`school`, inObjSchoolParams);
  return response.data || response;
}

/**
 * Creates a new book for a school.
 * @param inSchoolId
 * @param inObjBookParams
 * @returns {Promise<any>}
 */
async function createBook(inSchoolId, inObjBookParams) {
  const response = await client.post(
          `school/${inSchoolId}/book`,
          inObjBookParams
  );
  return response.data || response;
}

/**
 * Creates a new team member for a book.
 * @param inStrBookId
 * @param inObjUserParams
 * @returns {Promise<any>}
 */
async function createTeamMember(inStrBookId, inObjUserParams) {
  const response = await client.post(
          `book/${inStrBookId}/team/user`,
          inObjUserParams
  );
  return response.data || response;
}

/**
 * This function demonstrates how to automate the 
 * setup of a school, book and team members.
 *
 * It can be adapted as necessary to your own in-house workflows.
 */
(async function () {
  // schoolID must be unique. 
  // for testing, we use a random id.
  // for production use, remove this and 
  // call the 'doesSchoolExist' API to check if the school ID is in use
  const outSchoolParams = {
    name: 'Acme School',
    schoolID: 'acmeschool' + Math.floor(100000 + Math.random() * 900000),
  };
  // to create a book, we need to associate it with a book type, which provides
  // settings such as the form factor, page limit, etc.
  // in this example we are going to use the book type name to lookup the book type id
  // if you already have the id, this step can be skipped
  const bookTypeName = '8.5 x 11 Softcover';
  const bookTypes = await getBookTypes();
  const bookTypeId = bookTypes.data.find(
          (bookType) => get(bookType, 'attributes.name') === bookTypeName
  ).id;
  if (!bookTypeId) {
    throw new Error('book type id not found');
  }
  // for production use, set realistic book/cover deadlines
  const someFutureDate = moment().add(8, 'months').toISOString();
  // setup the book with a soft cover + hard cover. 
  // To add all available covers / inserts / endsheets omit the coverSettings param
  const coverSettings = [
    {
      pageTypeKey: 'softCover'	
    },
    {
      pageTypeKey: 'hardCover'
    }
  ]
  // sample book params - update as necessary
  const outBookParams = {
    bookDueDate: someFutureDate,
    bookFixedPageCount: null,
    coverDueDate: someFutureDate,
    coverSettings,
    bookTypeId,
    bookName: 'Acme School Yearbook',
    adminFirstName: 'John',
    adminLastName: 'Doe',
    adminEmail: 'johndoe@example.com',
  };
  // sample school admin params - update as necessary.
  // this user will be sent an invitation e-mail to accept
  // the Memento Yearbook terms of use, school COPPA regulations
  // and set an account password.
  const outUserParams = {
    first_name: 'Test',
    last_name: 'User',
    email: 'johndoe@example.com',
    password: 'password',
    role: 'editor_in_chief',
    username: 'someusername',
  };
  try {
    // here we are going to setup a school, book and add a new team member.
    // error control here is simplified for brevity. For production use, you'll want
    // to handle errors at each step.
    const school = await createSchool(outSchoolParams);
    const book = await createBook(get(school, 'data.id'), outBookParams);
    const teamUser = await createTeamMember(
            get(book, 'data.id'),
            outUserParams
    );
  } catch (error) {
    const errorReport =
            get(error, 'response.data.meta') || get(error, 'response.data') || error;
    console.error(`*** error occurred ***`);
    console.log(errorReport);
    throw new Error(error);
  }
})();