H1: corrected a round-1 over-reach. Bans are read-only per USER_JOURNEYS §10:
the base AuthUser extractor no longer rejects banned users (they keep read
access); Require{Host,Admin} and write handlers enforce the ban via
auth.is_banned → 403 (incl. self-unban). Demoted hosts still lose powers
immediately (live DB role) and are bounced off the dashboard. Also fixed the
login/recover redirect regression on unauthenticated 401.
H2: failed compression now self-heals instead of leaving a permanent broken
card — refund the quota, delete the orphaned original, soft-delete the row;
the frontend toasts the uploader and evicts the card on upload-error.
H3: self-delete, ban-with-hide (user-hidden), and comment-deletes now broadcast
SSE; feed, diashow, and lightbox evict the affected content live instead of
waiting for a reload. sse-eviction.spec.ts covers it.
Riders: feed/+page.svelte also carries the medium truncated-delta pill; host.rs
also carries the low atomic release_gallery claim; admin/+page.svelte also drops
the dead compression_concurrency control (medium).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
/**
|
|
* Regression for the review's H3: self-deleting an upload and banning a user with
|
|
* hide_uploads mutated visibility server-side but broadcast nothing, so the
|
|
* content lingered on every other viewer's feed (and the projector diashow) until
|
|
* a manual reload. Both now emit SSE so clients evict live.
|
|
*/
|
|
import { test, expect } from '../../fixtures/test';
|
|
import { seedUpload } from '../../helpers/seed';
|
|
import { SseListener } from '../../helpers/sse-listener';
|
|
|
|
const BASE = process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101';
|
|
|
|
test.describe('Host — live SSE eviction (H3)', () => {
|
|
test('self-deleting an upload broadcasts upload-deleted', async ({ guest }) => {
|
|
const g = await guest('SelfDeleter');
|
|
const uploadId = await seedUpload(g.jwt, { caption: 'delete me' });
|
|
|
|
const sse = new SseListener();
|
|
await sse.start(g.jwt);
|
|
|
|
const res = await fetch(`${BASE}/api/v1/upload/${uploadId}`, {
|
|
method: 'DELETE',
|
|
headers: { Authorization: `Bearer ${g.jwt}` },
|
|
});
|
|
expect(res.status).toBe(204);
|
|
|
|
await sse.waitForEvent(
|
|
'upload-deleted',
|
|
(e) => e.data.upload_id === uploadId
|
|
);
|
|
});
|
|
|
|
test('banning a user with hide_uploads broadcasts user-hidden', async ({
|
|
api,
|
|
host,
|
|
guest,
|
|
}) => {
|
|
const target = await guest('HideTarget');
|
|
await seedUpload(target.jwt, { caption: 'should vanish' });
|
|
|
|
const sse = new SseListener();
|
|
await sse.start(host.jwt);
|
|
|
|
await api.banUser(host.jwt, target.userId, true);
|
|
|
|
await sse.waitForEvent(
|
|
'user-hidden',
|
|
(e) => e.data.user_id === target.userId
|
|
);
|
|
});
|
|
});
|