Files
task-stream/libs/api/tracker/presenters/src/lib/http/proposal-limiter.ts
Josep Piguillem c91df110f8 feat: steadier live updates and notices for public proposals
- The web pings the live connection, reconnects when it goes quiet and
  reloads what each screen shows after reconnecting.
- Dropping a card on a column it cannot go to puts it back.
- Owners and admins get a notice when a proposal arrives.
- The proposal limit lives in Redis, so it holds across instances.
- REST tells whether I voted; pasted images leave the attachment list.
- Deleting a project removes its Git integration; no lint warnings left.
- Reactions and the blocked badge are components of their own.
- Docs: what is done and what is left (chapter 10), Word and PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:57:21 +02:00

25 lines
789 B
TypeScript

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { RateLimitStore } from '@task-stream/api/tracker/application';
/** Proposals from one address within the window. */
const LIMIT = 5;
const WINDOW_MS = 15 * 60_000;
/**
* Keeps one address from flooding a project with proposals. Counted in
* Redis, so the limit holds across every instance of the API.
*/
@Injectable()
export class ProposalLimiter {
constructor(private readonly store: RateLimitStore) {}
async take(address: string): Promise<void> {
const count = await this.store.hit(`proposals:${address}`, WINDOW_MS);
if (count > LIMIT)
throw new HttpException(
'Too many proposals from here; try again in a few minutes',
HttpStatus.TOO_MANY_REQUESTS,
);
}
}