fet: Add changes lol

Signed-off-by: Florian BOUILLON <f.bouillon@aptatio.com>
This commit is contained in:
2023-06-28 17:30:18 +02:00
parent 9530be5c43
commit ff07f8f4a5
24 changed files with 656 additions and 511 deletions

3
src/libs/README.md Normal file
View File

@ -0,0 +1,3 @@
# Libs
Globally independent objects/classes/functions that MUST be unit testable by themselve

View File

@ -1,6 +1,6 @@
import { objectLoop } from '@dzeio/object-util'
import StatusCode from './HTTP/StatusCode'
import { buildRFC7807 } from './RFCs/RFC7807'
import StatusCode from './StatusCode'
import ResponseBuilder from './ResponseBuilder'
interface StorageItem {
pointsRemaining: number
@ -33,36 +33,40 @@ export default class RateLimiter {
private storage: Record<string, StorageItem> = {}
public constructor(
private points = RateLimiter.points,
private timeSpan = RateLimiter.timeSpan
) {}
public consume(key: string, value: number = 1): Response | RateLimitHeaders {
let item = this.storage[key]
const now = (new Date().getTime() / 1000)
if (!item) {
item = {
pointsRemaining: RateLimiter.points,
timeReset: now + RateLimiter.timeSpan
pointsRemaining: this.points,
timeReset: now + this.timeSpan
}
}
if (item.timeReset <= now) {
item.timeReset = now + RateLimiter.timeSpan
item.pointsRemaining = RateLimiter.points
item.timeReset = now + this.timeSpan
item.pointsRemaining = this.points
}
item.pointsRemaining -= value
this.storage[key] = item
const headers: RateLimitHeaders = {
"X-RateLimit-Limit": RateLimiter.points.toFixed(0),
"X-RateLimit-Limit": this.points.toFixed(0),
"X-RateLimit-Remaining": Math.max(item.pointsRemaining, 0).toFixed(0),
"X-RateLimit-Reset": item.timeReset.toFixed(0)
}
if (item.pointsRemaining < 0) {
const res = new ResponseBuilder()
const resp = buildRFC7807({
type: '/docs/error/rate-limited',
status: StatusCode.TOO_MANY_REQUESTS,
title: 'You are being rate limited as you have done too many requests to the server'
})
resp.headers.append('Retry-After', (item.timeReset - now).toFixed(0))
objectLoop(headers, (value, key) => {
resp.headers.append(key, value)
})
}, res)
res.addHeader('Retry-After', (item.timeReset - now).toFixed(0))
res.addHeaders(headers as any)
return resp
}
return headers

View File

@ -18,18 +18,14 @@ export default class ResponseBuilder {
return this
}
private _headers: Headers = new Headers()
public headers(headers: HeadersInit ) {
if (headers instanceof Headers) {
this._headers = headers
} else {
this._headers = new Headers(headers)
}
private _headers: Record<string, string> = {}
public headers(headers: Record<string, string>) {
this._headers = headers
return this
}
public addHeader(key: string, value: string) {
this._headers.append(key, value)
this._headers[key] = value
return this
}
@ -41,7 +37,7 @@ export default class ResponseBuilder {
}
public removeHeader(key: string) {
this._headers.delete(key)
delete this._headers[key]
return this
}

View File

@ -4,7 +4,7 @@
* @returns a number if parsing happened correctly or undefined
*/
function parseNumber(str: string): number | undefined {
if (!/^(\d|\.)+$/g.test(str)) {
if (!/^-?(\d|\.)+$/g.test(str)) {
return undefined
}
const float = parseFloat(str)
@ -53,10 +53,10 @@ export function getParams(data: string) {
const lines = data.split('\n').filter((it) => it.startsWith(';') && it.includes('='))
// create the config object
const obj: Record<string, string | number> = {}
// loop through eacj config
// loop through each config
for (const line of lines) {
// get its key and value
const [key, value] = line.split('=', 2).map((it) => it.slice(1).trim())
const [key, value] = line.slice(1).split(/ *= */, 2).map((it) => it.trim())
// sip if it has no key or value
if (!key || !value) {
continue
@ -78,10 +78,10 @@ export function getParams(data: string) {
if (offset > 0) {
realKey = `${realKey}_${offset}`
}
// detect key collisions
if (obj[realKey] && obj[realKey] !== realValue) {
throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
}
// detect key collisions (it will never happens with the while above)
// if (obj[realKey] && obj[realKey] !== realValue) {
// throw new Error(`Key collision ${key}=${realValue} ${realKey}=${obj[realKey]}`)
// }
// set the value to the key
obj[realKey] = realValue
// transform the time to a number of seconds