Files
bitsforfree/src/uploadLimits.js
T
2026-05-08 18:18:36 -03:00

67 lines
2.2 KiB
JavaScript

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: {} };
}
}