Skip to content

Commit d4e0592

Browse files
NiallJoeMaherclaude
andcommitted
Fix E2E test failures and hide banned users' posts from feeds
Fix editor "Saved" indicator for new posts by setting savedTime in the CREATE branch. Add test.slow() for Firefox publish test timeout. Replace force-click bookmark buttons with proper TRPC mutation response waits. Hide banned users' posts by updating status to draft on ban and restoring on unban. Add defense-in-depth LEFT JOIN on banned_users to post and feed query endpoints. Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent c4fb779 commit d4e0592

6 files changed

Lines changed: 65 additions & 6 deletions

File tree

app/(editor)/create/[[...paramsArr]]/_client.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ const CreateContent = ({ session }: { session: Session | null }) => {
243243
published: false,
244244
});
245245
setUnsavedChanges(false);
246+
setSavedTime(
247+
new Date().toLocaleString(undefined, {
248+
dateStyle: "medium",
249+
timeStyle: "short",
250+
}),
251+
);
246252
return result.id;
247253
} else {
248254
await save({

e2e/articles.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ test.describe("Authenticated Feed Page (Articles)", () => {
189189
});
190190

191191
test("Should write and publish an article", async ({ page, isMobile }) => {
192+
test.slow();
192193
const articleTitle = "Lorem Ipsum";
193194
await page.goto("http://localhost:3000");
194195
// Waits for articles to be loaded
@@ -349,15 +350,21 @@ test.describe("Authenticated Feed Page (Articles)", () => {
349350
if (isSaved) {
350351
// Article is already bookmarked - unbookmark then rebookmark to test the flow
351352
await savedButton.scrollIntoViewIfNeeded();
352-
await savedButton.click({ force: true });
353+
await Promise.all([
354+
page.waitForResponse(resp => resp.url().includes('trpc') && resp.url().includes('bookmark')),
355+
savedButton.click(),
356+
]);
353357
await expect(saveButton).toBeVisible({ timeout: 15000 });
354358
}
355359

356360
// Now bookmark the article
357361
await expect(saveButton).toBeVisible({ timeout: 15000 });
358362
await expect(saveButton).toBeEnabled({ timeout: 5000 });
359363
await saveButton.scrollIntoViewIfNeeded();
360-
await saveButton.click({ force: true });
364+
await Promise.all([
365+
page.waitForResponse(resp => resp.url().includes('trpc') && resp.url().includes('bookmark')),
366+
saveButton.click(),
367+
]);
361368

362369
// Wait for button text to change to "Saved" after React state update
363370
await expect(savedButton).toBeVisible({ timeout: 30000 });

e2e/saved.spec.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,20 @@ test.describe("Authenticated Saved Page", () => {
5151
const isSaved = await savedButton.isVisible().catch(() => false);
5252
if (isSaved) {
5353
await savedButton.scrollIntoViewIfNeeded();
54-
await savedButton.click({ force: true });
54+
await Promise.all([
55+
page.waitForResponse(resp => resp.url().includes('trpc') && resp.url().includes('bookmark')),
56+
savedButton.click(),
57+
]);
5558
await expect(saveButton).toBeVisible({ timeout: 10000 });
5659
}
5760

5861
// Now bookmark it
5962
await expect(saveButton).toBeVisible({ timeout: 15000 });
6063
await saveButton.scrollIntoViewIfNeeded();
61-
await saveButton.click({ force: true });
64+
await Promise.all([
65+
page.waitForResponse(resp => resp.url().includes('trpc') && resp.url().includes('bookmark')),
66+
saveButton.click(),
67+
]);
6268

6369
// Wait for the saved state to appear - this confirms the bookmark mutation succeeded
6470
await expect(savedButton).toBeVisible({ timeout: 15000 });

server/api/router/admin.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
content_report,
1212
feed_sources,
1313
} from "@/server/db/schema";
14-
import { and, count, desc, eq, sql } from "drizzle-orm";
14+
import { and, count, desc, eq, isNotNull, sql } from "drizzle-orm";
1515

1616
export const adminRouter = createTRPCRouter({
1717
// Get dashboard stats
@@ -166,6 +166,12 @@ export const adminRouter = createTRPCRouter({
166166

167167
await ctx.db.delete(session).where(eq(session.userId, userId));
168168

169+
// Hide all published posts by the banned user
170+
await ctx.db
171+
.update(posts)
172+
.set({ status: "draft" })
173+
.where(and(eq(posts.authorId, userId), eq(posts.status, "published")));
174+
169175
return { banned: true };
170176
}),
171177
unban: adminOnlyProcedure
@@ -175,6 +181,18 @@ export const adminRouter = createTRPCRouter({
175181

176182
await ctx.db.delete(banned_users).where(eq(banned_users.userId, userId));
177183

184+
// Restore posts that were previously published (have publishedAt set)
185+
await ctx.db
186+
.update(posts)
187+
.set({ status: "published" })
188+
.where(
189+
and(
190+
eq(posts.authorId, userId),
191+
eq(posts.status, "draft"),
192+
isNotNull(posts.publishedAt),
193+
),
194+
);
195+
178196
return { unbanned: true };
179197
}),
180198
});

server/api/router/feed.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
feed_sources,
3131
tag,
3232
post_tags,
33+
banned_users,
3334
} from "@/server/db/schema";
3435
import {
3536
and,
@@ -40,6 +41,7 @@ import {
4041
lt,
4142
sql,
4243
isNotNull,
44+
isNull,
4345
count,
4446
} from "drizzle-orm";
4547
import { increment } from "./utils";
@@ -156,11 +158,18 @@ export const feedRouter = createTRPCRouter({
156158
) as typeof query;
157159
}
158160

161+
// Add banned users join for defense-in-depth filtering
162+
query = query.leftJoin(
163+
banned_users,
164+
eq(posts.authorId, banned_users.userId),
165+
) as typeof query;
166+
159167
// Build where conditions - only link type posts with sources
160168
const whereConditions = [
161169
eq(posts.type, "link"),
162170
eq(posts.status, "published"),
163171
isNotNull(posts.sourceId),
172+
isNull(banned_users.userId),
164173
category ? eq(feed_sources.category, category) : undefined,
165174
cursorCondition,
166175
].filter(Boolean);
@@ -784,10 +793,17 @@ export const feedRouter = createTRPCRouter({
784793
) as typeof query;
785794
}
786795

796+
// Add banned users join for defense-in-depth filtering
797+
query = query.leftJoin(
798+
banned_users,
799+
eq(posts.authorId, banned_users.userId),
800+
) as typeof query;
801+
787802
// Build where conditions
788803
const whereConditions = [
789804
eq(posts.sourceId, source.id),
790805
eq(posts.status, "published"),
806+
isNull(banned_users.userId),
791807
cursorCondition,
792808
].filter(Boolean);
793809

server/api/router/post.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
comments,
2828
tag,
2929
user,
30+
banned_users,
3031
} from "@/server/db/schema";
3132
import {
3233
and,
@@ -100,7 +101,10 @@ export const postRouter = createTRPCRouter({
100101
const scoreExpr = sql<number>`(${posts.upvotesCount} - ${posts.downvotesCount})`;
101102

102103
// Build conditions
103-
const conditions = [eq(posts.status, "published")];
104+
const conditions = [
105+
eq(posts.status, "published"),
106+
isNull(banned_users.userId),
107+
];
104108

105109
if (type) {
106110
conditions.push(eq(posts.type, type));
@@ -196,6 +200,7 @@ export const postRouter = createTRPCRouter({
196200
.from(posts)
197201
.leftJoin(feedSources, eq(posts.sourceId, feedSources.id))
198202
.leftJoin(user, eq(posts.authorId, user.id))
203+
.leftJoin(banned_users, eq(posts.authorId, banned_users.userId))
199204
.leftJoin(userVotesSubquery, eq(posts.id, userVotesSubquery.postId))
200205
.leftJoin(
201206
userBookmarksSubquery,
@@ -244,6 +249,7 @@ export const postRouter = createTRPCRouter({
244249
.from(posts)
245250
.leftJoin(feedSources, eq(posts.sourceId, feedSources.id))
246251
.leftJoin(user, eq(posts.authorId, user.id))
252+
.leftJoin(banned_users, eq(posts.authorId, banned_users.userId))
247253
.where(and(...conditions))
248254
.orderBy(orderBy)
249255
.limit(limit + 1);

0 commit comments

Comments
 (0)