186 lines
4.0 KiB
JavaScript
186 lines
4.0 KiB
JavaScript
'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) {
|
|
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?.TTL || ttl})
|
|
ON CONFLICT("Route", "Source") DO UPDATE SET "Count"=1, "TTL"=${ttl}`
|
|
)
|
|
.then(() => {
|
|
cb(null, {
|
|
current: 1,
|
|
ttl: 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({ port: 3000 }, err => {
|
|
if (err) throw err
|
|
console.log('Server listening at http://localhost:3000')
|
|
})
|