Ghost/ghost/limit-service
Naz 1802d46c1d Added a limit reset when loadLimits called repeatedly
refs https://github.com/TryGhost/Team/issues/599

- There are cases when there'a a need to reload limits with a new set of configuration. For example, when Ghost is run in a test environment is a soft reboot is done
- Resetting previous value of limits avoids having conflicting state after multiple calls
2021-04-09 23:44:12 +12:00
..
lib Added a limit reset when loadLimits called repeatedly 2021-04-09 23:44:12 +12:00
test Added a limit reset when loadLimits called repeatedly 2021-04-09 23:44:12 +12:00
.eslintrc.js Added limit service initial commit 2021-03-03 12:19:48 +00:00
index.js Added limit service initial commit 2021-03-03 12:19:48 +00:00
LICENSE Added limit service initial commit 2021-03-03 12:19:48 +00:00
package.json Published new versions 2021-04-07 13:47:32 +12:00
README.md Improved docs around {{max}} & {{count}} 2021-04-07 18:14:18 +12:00

Limit Service

This module is intended to hold all of the logic for testing if site:

  • would be over a given limit if they took an action (i.e. added one more thing, switched to a different limit)
  • if they are over a limit already
  • consistent error messages explaining why the limit has been reached

Install

npm install @tryghost/limit-service --save

or

yarn add @tryghost/limit-service

Usage

Below is a sample code to wire up limit service and perform few common limit checks:

const errors = require('@tryghost/errors');
const LimitService = require('@tryghost/limit-service');

// create a LimitService instance
const limitService = new LimitService();

// setup limit configuration
// currently supported limit keys are: staff, members, customThemes, customIntegrations
// all limit configs support custom "error" configuration that is a template string
const limits = {
    // staff and member are "max" type of limits accepting "max" configuration
    staff: {
        max: 1,
        error: 'Your plan supports up to {{max}} staff users. Please upgrade to add more.'
    },
    members: {
        max: 1000,
        error: 'Your plan supports up to {{max}} members. Please upgrade to reenable publishing.'
    },
    // customThemes and customIntegrations are "flag" type of limits accepting disabled boolean configuration
    customThemes: {
        disabled: true,
        error: 'All our official built-in themes are available the Starter plan, if you upgrade to one of our higher tiers you will also be able to edit and upload custom themes for your site.'
    },
    customIntegrations: {
        disabled: true,
        error: 'You can use all our official, built-in integrations on the Starter plan. If you upgrade to one of our higher tiers, youll also be able to create and edit custom integrations and API keys for advanced workflows.'
    }
};

// initialize the URL linking to help documentation etc.
const helpLink = 'https://ghost.org/help/';

// initialize knex db connection for the limit service to use when running query checks
const db = knex({
    client: 'mysql',
    connection: {
        user: 'root',
        password: 'toor',
        host: 'localhost',
        database: 'ghost',
    }
});

// finish initializing the limits service
limitService.loadLimits({limits, db, helpLink, errors});

// perform limit checks

// check if there is a 'staff' limit configured
if (limitService.isLimited('staff')) {
    // throws an error if current 'staff' limit **would** go over the limit set up in configuration (max:1)
    await limitService.errorIfWouldGoOverLimit('staff');

    // same as above but overrides the default max check from max of 1 to 100
    // useful in cases you need to check if specific instance would still be over the limit if the limit changed
    await limitService.errorIfWouldGoOverLimit('staff', {max: 100});
}

// "max" types of limits have currentCountQuery method reguring a number that is currently in use for the limit
// for example it could be 1, 3, 5 or whatever amount of 'staff' is currently in the system
const staffCount = await limitService.currentCountQuery('staff');

// do something with that number
console.log(`Your current staff count is at: ${staffCount}!`);

// check if there is a 'members' limit configured
if (limitService.isLimited('members')) {
    // throws an error if current 'staff' limit **is** over the limit set up in configuration (max: 1000)
    await limitService.errorIfIsOverLimit('members');

    // same as above but overrides the default max check from max of 1000 to 10000
    // useful in cases you need to check if specific instance would still be over the limit if the limit changed
    await limitService.errorIfIsOverLimit('members', {max: 10000});
}

In case the limit check is run without direct access to the database you can override currentCountQuery functions for each "max" type of limit. An example usecase would be a frontend client running in a browser. A browser client can check the limit data through HTTP request and then provide that data to the limit service. Example code to do exactly that:

const limitService = new LimitService();

let limits = {
    staff: {
        max: 2,
        currentCountQuery: async () => (await fetch('/api/staff')).json().length
    }
};

limitService.loadLimits({limits, errors});

if (await limitService.checkIsOverLimit('staff')) {
    // do something as "staff" limit has been reached
};

Custom error messages

Errors returned by the limit service can be customized. When configuring the limit service through loadLimits method limits objects can specify an error property that is a template string. Additionally, "MaxLimit" limit type supports following variables- {{count}} and {{max}}.

An example configuration for "MaxLimit" limit using an error template can look like following:

"staff": {
    "max": 5,
    "error": "Your plan supports up to {{max}} staff users and you currently have {{count}}. Please upgrade to add more."
}

Develop

This is a mono repository, managed with lerna.

Follow the instructions for the top-level repo.

  1. git clone this repo & cd into it as usual
  2. Run yarn to install top-level dependencies.

Run

  • yarn dev

Test

  • yarn lint run just eslint
  • yarn test run lint and tests

Copyright & License

Copyright (c) 2013-2021 Ghost Foundation - Released under the MIT license.