init
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { HttpError } from './errors.js';
|
||||
|
||||
const HOUR_LIMIT = 5;
|
||||
const DAY_LIMIT = 10;
|
||||
const GLOBAL_DAY_LIMIT = 100;
|
||||
|
||||
export async function createUploadLimiter({ dataDir }) {
|
||||
const filePath = path.join(path.resolve(dataDir), 'index', 'upload-limits.json');
|
||||
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
||||
let state = await loadState(filePath);
|
||||
let writeChain = Promise.resolve();
|
||||
|
||||
return {
|
||||
async checkAndConsume(ip) {
|
||||
const now = new Date();
|
||||
const day = now.toISOString().slice(0, 10);
|
||||
const hour = now.toISOString().slice(0, 13);
|
||||
const key = crypto.createHash('sha256').update(ip || 'unknown').digest('hex');
|
||||
|
||||
if (state.day !== day) state = { day, global: 0, clients: {} };
|
||||
const client = state.clients[key] || { day, dayCount: 0, hour, hourCount: 0 };
|
||||
if (client.day !== day) {
|
||||
client.day = day;
|
||||
client.dayCount = 0;
|
||||
}
|
||||
if (client.hour !== hour) {
|
||||
client.hour = hour;
|
||||
client.hourCount = 0;
|
||||
}
|
||||
|
||||
if (state.global >= GLOBAL_DAY_LIMIT) {
|
||||
throw new HttpError(429, 'Daily site upload budget reached. Try again tomorrow.');
|
||||
}
|
||||
if (client.hourCount >= HOUR_LIMIT) {
|
||||
throw new HttpError(429, 'Upload limit reached: 5 images per hour.');
|
||||
}
|
||||
if (client.dayCount >= DAY_LIMIT) {
|
||||
throw new HttpError(429, 'Upload limit reached: 10 images per day.');
|
||||
}
|
||||
|
||||
state.global += 1;
|
||||
client.hourCount += 1;
|
||||
client.dayCount += 1;
|
||||
state.clients[key] = client;
|
||||
writeChain = writeChain.then(() => fs.writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`));
|
||||
await writeChain;
|
||||
|
||||
return {
|
||||
remainingHour: Math.max(0, HOUR_LIMIT - client.hourCount),
|
||||
remainingDay: Math.max(0, DAY_LIMIT - client.dayCount),
|
||||
remainingGlobalDay: Math.max(0, GLOBAL_DAY_LIMIT - state.global)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function loadState(filePath) {
|
||||
try {
|
||||
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
||||
} catch {
|
||||
return { day: new Date().toISOString().slice(0, 10), global: 0, clients: {} };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user