Ghost/core/server/api
Fabien O'Carroll 93b1035df5 🐛 Fixed Member model removing labels when unset
closes https://github.com/TryGhost/Ghost/issues/12600

The bookshelf-relations plugin which we use will **remove** all
relations when they are set to an empty array, but will leave them alone
if it's set to undefined.

Our logic to deduplicate uppercase & lowercase version of the same label
was in advertently always setting the labels to an array, but when the
model was saved without passing the labels, this array would be empty.

Here we've added a check which will skip all label handling, if there
are no labels set.
2021-02-08 09:39:46 +00:00
..
canary 🐛 Fixed Member model removing labels when unset 2021-02-08 09:39:46 +00:00
shared Added .yaml format support in redirects configuration (#12187) 2020-11-04 12:08:32 +13:00
v2 Enabled Members for all sites (#12582) 2021-02-02 16:12:53 +00:00
v3 Enabled Members for all sites (#12582) 2021-02-02 16:12:53 +00:00
index.js Migrate the default api version from v2 to v3 2021-02-03 15:02:51 +01:00
README.md Updated API versioning README.md 2019-09-12 16:27:09 +02:00

API Versioning

Ghost supports multiple API versions. Each version lives in a separate folder e.g. api/v2, api/v3, api/canary etc. Next to the API folders there is a shared folder, which contains shared code, which all API versions use.

Stages

Each request goes through the following stages:

  • input validation
  • input serialisation
  • permissions
  • query
  • output serialisation

The framework we are building pipes a request through these stages in respect of the API controller configuration.

Frame

Is a class, which holds all the information for request processing. We pass this instance by reference. Each function can modify the original instance. No need to return the class instance.

Structure

{
  original: Object,
  options: Object,
  data: Object,
  user: Object,
  file: Object,
  files: Array
}

Example

{
  original: {
    include: 'tags'
  },
  options: {
    withRelated: ['tags']
  },
  data: {
    posts: []
  }
}

API Controller

A controller is no longer just a function, it's a set of configurations.

Structure

edit: function || object
edit: {
  headers: object,
  options: Array,
  data: Array,
  validation: object | function,
  permissions: boolean | object | function,
  query: function
}

Examples

edit: {
  headers: {
    cacheInvalidate: true
  },
  // Allowed url/query params
  options: ['include']
  // Url/query param validation configuration
  validation: {
    options: {
      include: {
        required: true,
        values: ['tags']
      }
    }
  },
  permissions: true,
  // Returns a model response!
  query(frame) {
    return models.Post.edit(frame.data, frame.options);
  }
}
read: {
  // Allowed url/query params, which will be remembered inside `frame.data`
  // This is helpful for READ requests e.g. `model.findOne(frame.data, frame.options)`.
  // Our model layer requires sending the where clauses as first parameter.
  data: ['slug']
  validation: {
    data: {
      slug: {
        values: ['eins']
      }
    }
  },
  permissions: true,
  query(frame) {
    return models.Post.findOne(frame.data, frame.options);
  }
}
edit: {
  validation() {
    // custom validation, skip framework
  },
  permissions: {
    unsafeAttrs: ['author']
  },
  query(frame) {
    return models.Post.edit(frame.data, frame.options);
  }
}