-
-
Notifications
You must be signed in to change notification settings - Fork 75
added example implementing custom store using Sequelize ORM for PostgreSQL database; renamed example-custom to example-knex; added links of examples implementing custom-store to README.md #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
914be22
8df92b1
e6a1888
be8c0b3
ebcef63
27ef924
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| 'use strict' | ||
|
|
||
| // Example of a Custom Store using Sequelize ORM for PostgreSQL database | ||
|
|
||
| // Sequelize Migration for "RateLimits" table | ||
| // | ||
| // module.exports = { | ||
| // up: (queryInterface, { TEXT, INTEGER, BIGINT }) => { | ||
| // return queryInterface.createTable( | ||
| // 'RateLimits', | ||
| // { | ||
| // Route: { | ||
| // type: TEXT, | ||
| // allowNull: false | ||
| // }, | ||
| // Source: { | ||
| // type: TEXT, | ||
| // allowNull: false, | ||
| // primaryKey: true | ||
| // }, | ||
| // Count: { | ||
| // type: INTEGER, | ||
| // allowNull: false | ||
| // }, | ||
| // TTL: { | ||
| // type: BIGINT, | ||
| // allowNull: false | ||
| // } | ||
| // }, | ||
| // { | ||
| // freezeTableName: true, | ||
| // timestamps: false, | ||
| // uniqueKeys: { | ||
| // unique_tag: { | ||
| // customIndex: true, | ||
| // fields: ['Route', 'Source'] | ||
| // } | ||
| // } | ||
| // } | ||
| // ) | ||
| // }, | ||
| // down: queryInterface => { | ||
| // return queryInterface.dropTable('RateLimits') | ||
| // } | ||
| // } | ||
|
|
||
| const fastify = require('fastify')() | ||
| const Sequelize = require('sequelize') | ||
|
|
||
| const databaseUri = 'postgres://username:password@localhost:5432/fastify-rate-limit-example' | ||
| const sequelize = new Sequelize(databaseUri) | ||
| // OR | ||
| // const sequelize = new Sequelize('database', 'username', 'password'); | ||
|
|
||
| // Sequelize Model for "RateLimits" table | ||
| // | ||
| const RateLimits = sequelize.define( | ||
| 'RateLimits', | ||
| { | ||
| Route: { | ||
| type: Sequelize.TEXT, | ||
| allowNull: false | ||
| }, | ||
| Source: { | ||
| type: Sequelize.TEXT, | ||
| allowNull: false, | ||
| primaryKey: true | ||
| }, | ||
| Count: { | ||
| type: Sequelize.INTEGER, | ||
| allowNull: false | ||
| }, | ||
| TTL: { | ||
| type: Sequelize.BIGINT, | ||
| allowNull: false | ||
| } | ||
| }, | ||
| { | ||
| freezeTableName: true, | ||
| timestamps: false, | ||
| indexes: [ | ||
| { | ||
| unique: true, | ||
| fields: ['Route', 'Source'] | ||
| } | ||
| ] | ||
| } | ||
| ) | ||
|
|
||
| function RateLimiterStore (options) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that moving this class to a file would let the user understand better how to organize the source. Moreover, this would avoid having a global (I saw there is the same issue in the knex example and I think it should be improved as well - not an issue with this PR) |
||
| this.options = options | ||
| this.route = '' | ||
| } | ||
|
|
||
| RateLimiterStore.prototype.routeKey = function routeKey (route) { | ||
| if (route) this.route = route | ||
| return route | ||
| } | ||
|
|
||
| RateLimiterStore.prototype.incr = async function incr (key, cb) { | ||
| const now = new Date().getTime() | ||
| const ttl = now + this.options.timeWindow | ||
| const cond = { Route: this.route, Source: key } | ||
|
|
||
| const RateLimit = await RateLimits.findOne({ where: cond }) | ||
|
|
||
| if (RateLimit && parseInt(RateLimit.TTL, 10) > now) { | ||
| try { | ||
| await RateLimit.update({ Count: RateLimit.Count + 1 }, cond) | ||
| cb(null, { | ||
| current: RateLimit.Count + 1, | ||
| ttl: RateLimit.TTL | ||
| }) | ||
| } catch (err) { | ||
| cb(err, { | ||
| current: 0 | ||
| }) | ||
| } | ||
| } else { | ||
| sequelize.query( | ||
| `INSERT INTO "RateLimits"("Route", "Source", "Count", "TTL") | ||
| VALUES('${this.route}', '${key}', 1, | ||
| ${(RateLimit && RateLimit.TTL) || ttl}) | ||
| ON CONFLICT("Route", "Source") DO UPDATE SET "Count"=1, "TTL"=${ttl}` | ||
| ) | ||
| .then(() => { | ||
| cb(null, { | ||
| current: 1, | ||
| ttl: (RateLimit && RateLimit.TTL) || ttl | ||
| }) | ||
| }) | ||
| .catch(err => { | ||
| cb(err, { | ||
| current: 0 | ||
| }) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| RateLimiterStore.prototype.child = function child (routeOptions = {}) { | ||
| const options = Object.assign(this.options, routeOptions) | ||
| const store = new RateLimiterStore(options) | ||
| store.routeKey(routeOptions.routeInfo.method + routeOptions.routeInfo.url) | ||
| return store | ||
| } | ||
|
|
||
| fastify.register(require('../../fastify-rate-limit'), | ||
| { | ||
| global: false, | ||
| max: 10, | ||
| store: RateLimiterStore, | ||
| skipOnError: false | ||
| } | ||
| ) | ||
|
|
||
| fastify.get('/', { | ||
| config: { | ||
| rateLimit: { | ||
| max: 10, | ||
| timeWindow: '1 minute' | ||
| } | ||
| } | ||
| }, (req, reply) => { | ||
| reply.send({ hello: 'from ... root' }) | ||
| }) | ||
|
|
||
| fastify.get('/private', { | ||
| config: { | ||
| rateLimit: { | ||
| max: 3, | ||
| timeWindow: '1 minute' | ||
| } | ||
| } | ||
| }, (req, reply) => { | ||
| reply.send({ hello: 'from ... private' }) | ||
| }) | ||
|
|
||
| fastify.get('/public', (req, reply) => { | ||
| reply.send({ hello: 'from ... public' }) | ||
| }) | ||
|
|
||
| fastify.listen(3000, err => { | ||
| if (err) throw err | ||
| console.log('Server listening at http://localhost:3000') | ||
| }) | ||
Uh oh!
There was an error while loading. Please reload this page.