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>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { REDIS_CLIENT } from '@task-stream/api/shared/infrastructure';
|
||||
import { RateLimitStore } from '@task-stream/api/tracker/application';
|
||||
import Redis from 'ioredis';
|
||||
|
||||
/** A fixed window per key: the first hit opens it, it expires on its own. */
|
||||
@Injectable()
|
||||
export class RedisRateLimitStore extends RateLimitStore {
|
||||
constructor(@Inject(REDIS_CLIENT) private readonly redis: Redis) {
|
||||
super();
|
||||
}
|
||||
|
||||
async hit(key: string, windowMs: number): Promise<number> {
|
||||
const name = `task-stream:rate:${key}`;
|
||||
const count = await this.redis.incr(name);
|
||||
if (count === 1) await this.redis.pexpire(name, windowMs);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
UpsertMaterializedReleaseRepository,
|
||||
UpsertMaterializedTimelineRepository,
|
||||
UserDirectory,
|
||||
RateLimitStore,
|
||||
} from '@task-stream/api/tracker/application';
|
||||
import { IdentityUserDirectory } from './identity/identity-user-directory';
|
||||
import {
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
import { PostgresReleaseIssuesRepository } from './postgres/release-issues.repository';
|
||||
import { PostgresTrackerStreamsRepository } from './postgres/tracker-streams.repository';
|
||||
import { RedisChangeFeed } from './realtime/redis-change-feed';
|
||||
import { RedisRateLimitStore } from './redis/redis-rate-limit.store';
|
||||
import { MongoNotificationsRepository } from './mongo/notifications.repository';
|
||||
import { MongoSavedFiltersRepository } from './mongo/saved-filters.repository';
|
||||
import { MongoGitIntegrationsRepository } from './mongo/git-integrations.repository';
|
||||
@@ -144,6 +146,7 @@ const PORTS = [
|
||||
{ provide: NotificationMailer, useClass: SmtpNotificationMailer },
|
||||
// Realtime → Redis pub/sub
|
||||
{ provide: ChangeFeed, useClass: RedisChangeFeed },
|
||||
{ provide: RateLimitStore, useClass: RedisRateLimitStore },
|
||||
// Identity context (anti-corruption layer)
|
||||
{ provide: UserDirectory, useClass: IdentityUserDirectory },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user