Ghost/ghost/limit-service
Naz 612cc2b513 Added addedCount to max and maxPeriodic limits
refs https://github.com/TryGhost/Team/issues/588

- The `addedCount` parameter in `errorIfWouldGoOverLimit` method allows to specify a custom resource count that is about to be added. Example usecase is when we'd want to send a 100 emails and current limit is 99, and none have been sent so far. With previous implementation the check would've passed because it only checked for single resource that would be added through "+1". Current implementation allows to specify the amount of recources to be added
2021-05-07 18:13:01 +04:00
..
lib Added addedCount to max and maxPeriodic limits 2021-05-07 18:13:01 +04:00
test Added addedCount to max and maxPeriodic limits 2021-05-07 18:13:01 +04: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-05-07 15:00:07 +04: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.