diff --git a/apps/chat/src/index.ts b/apps/chat/src/index.ts
index b91c3af..a4b6ed2 100644
--- a/apps/chat/src/index.ts
+++ b/apps/chat/src/index.ts
@@ -280,23 +280,32 @@ async function logModerationEvent(payload: {
});
}
-async function deleteMessageFromHistory(targetUsername: string, msgId: string): Promise {
+async function deleteMessageFromHistory(
+ targetUsername: string,
+ msgId: string
+): Promise<{ deleted: boolean; messageContent?: string }> {
const channelKey = `chat:history:${targetUsername}`;
const history = await redis.zrange(channelKey, 0, -1);
for (const entry of history) {
try {
- const parsed = JSON.parse(entry) as { msgId?: string };
+ const parsed = JSON.parse(entry) as { msgId?: string; message?: string };
if (parsed.msgId === msgId) {
await redis.zrem(channelKey, entry);
- return true;
+ return {
+ deleted: true,
+ messageContent:
+ typeof parsed.message === 'string' && parsed.message.length > 0
+ ? parsed.message
+ : undefined,
+ };
}
} catch {
continue;
}
}
- return false;
+ return { deleted: false };
}
const app = new Hono();
diff --git a/apps/chat/src/utils/moderation.ts b/apps/chat/src/utils/moderation.ts
index 9b848ce..cfe0479 100644
--- a/apps/chat/src/utils/moderation.ts
+++ b/apps/chat/src/utils/moderation.ts
@@ -26,7 +26,10 @@ type ModerationContext = {
};
type DeleteMessageDeps = {
- deleteMessageFromHistory: (targetUsername: string, msgId: string) => Promise;
+ deleteMessageFromHistory: (
+ targetUsername: string,
+ msgId: string
+ ) => Promise<{ deleted: boolean; messageContent?: string }>;
logModerationEvent: (payload: {
action: ChatModerationAction;
channelId: string;
@@ -258,8 +261,8 @@ export async function handleDeleteMessageCommand(
return;
}
- const deleted = await deps.deleteMessageFromHistory(context.targetUsername, msgId);
- if (!deleted) {
+ const deletedMessage = await deps.deleteMessageFromHistory(context.targetUsername, msgId);
+ if (!deletedMessage.deleted) {
sendModerationError(socket, 'NOT_FOUND', 'Message not found.');
return;
}
@@ -269,7 +272,10 @@ export async function handleDeleteMessageCommand(
channelId: context.channelId,
moderatorId: context.chatUser.moderatorUserId,
reason: 'Message deleted by moderator',
- details: { msgId },
+ details: {
+ msgId,
+ messageContent: deletedMessage.messageContent,
+ },
});
recordChatModerationAction('message_deleted');
diff --git a/apps/web/src/app/(ui)/(protected)/admin/page.client.tsx b/apps/web/src/app/(ui)/(protected)/admin/page.client.tsx
index bcc5a53..a772884 100644
--- a/apps/web/src/app/(ui)/(protected)/admin/page.client.tsx
+++ b/apps/web/src/app/(ui)/(protected)/admin/page.client.tsx
@@ -1019,6 +1019,20 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
{log.reason}
)}
+
+ {log.deletedMessageContent && (
+
+
+
+
+ Deleted message
+
+
+ {log.deletedMessageContent}
+
+
+
+ )}
);
@@ -1184,10 +1198,10 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
)}
- )}
-
- {activeTab === 'settings' && (
-
+ )}
+
+ {activeTab === 'settings' && (
+
}
title="Platform Settings"
@@ -1205,7 +1219,8 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
ID Verification Bypass
- Allow existing users to bypass HCA verification and let them access the platform.
+ Allow existing users to bypass HCA verification and let them access the
+ platform.
@@ -1231,16 +1246,15 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
Start searching to manage users
- Type an email or username above to find users and toggle their verification bypass
+ Type an email or username above to find users and toggle their
+ verification bypass
) : users.length === 0 ? (
No users found
-
- Try a different search term
-
+
Try a different search term
) : (
<>
@@ -1292,7 +1306,8 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
onClick={() => handleToggleBypassVerification(user.id)}
className={cn(
'h-7 text-xs gap-1 shrink-0 font-semibold transition-all duration-200',
- user.bypassVerification && 'border-primary/30 hover:bg-primary/10 hover:border-primary/50'
+ user.bypassVerification &&
+ 'border-primary/30 hover:bg-primary/10 hover:border-primary/50'
)}
>
{user.bypassVerification ? (
@@ -1327,7 +1342,8 @@ export default function AdminPanelClient({ currentUser }: AdminPanelClientProps)
Session Management
- Force logout all other sessions except your current one. Useful for security maintenance.
+ Force logout all other sessions except your current one. Useful for
+ security maintenance.
0 ? messageContent : null;
+}
+
export async function GET(request: NextRequest) {
const { user } = await validateRequest();
if (!user?.isAdmin) {
@@ -136,6 +145,7 @@ export async function GET(request: NextRequest) {
(log.targetUserId ? (targetUserMap.get(log.targetUserId) ?? log.targetUserId) : null),
reason: log.reason,
details: log.details,
+ deletedMessageContent: getDeletedMessageContent(log.details),
actorMeta: {
isPlatformAdmin: log.actor.isAdmin,
isChannelModerator: channelModSet.has(log.actorId),
@@ -157,6 +167,7 @@ export async function GET(request: NextRequest) {
target: log.targetUser?.personalChannel?.name ?? log.channel.name,
reason: log.reason,
details: log.details,
+ deletedMessageContent: getDeletedMessageContent(log.details),
channelName: log.channel.name,
actorMeta: {
isPlatformAdmin: log.moderator.isAdmin,
diff --git a/apps/web/src/app/(ui)/(protected)/api/admin/reports/route.ts b/apps/web/src/app/(ui)/(protected)/api/admin/reports/route.ts
index 7f5b6db..2c2560b 100644
--- a/apps/web/src/app/(ui)/(protected)/api/admin/reports/route.ts
+++ b/apps/web/src/app/(ui)/(protected)/api/admin/reports/route.ts
@@ -292,6 +292,7 @@ export async function POST(request: NextRequest) {
details: {
reportId,
msgId: report.reportedMessageId,
+ messageContent: report.reportedMessage,
} as any,
},
});
@@ -307,6 +308,7 @@ export async function POST(request: NextRequest) {
reportId,
enforcement: 'DELETE_REPORTED_MESSAGE',
msgId: report.reportedMessageId,
+ messageContent: report.reportedMessage,
} as any,
},
});
diff --git a/apps/web/src/components/app/StreamPlayer/StreamPlayer.tsx b/apps/web/src/components/app/StreamPlayer/StreamPlayer.tsx
index a30cbf2..0e9ebe1 100644
--- a/apps/web/src/components/app/StreamPlayer/StreamPlayer.tsx
+++ b/apps/web/src/components/app/StreamPlayer/StreamPlayer.tsx
@@ -200,7 +200,7 @@ export default function StreamPlayer() {
{(process.env.NODE_ENV === 'development' || userInfo?.isLive) && (
triggerRecovery('manual_reload')}>
-
+
Retry stream
diff --git a/package.json b/package.json
index 2e3e715..7131c27 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"docker:chat": "dotenvx run -f .env.docker -- docker buildx build --platform linux/amd64 -f apps/chat/Dockerfile . --secret id=TURBO_TOKEN,env=TURBO_TOKEN --secret id=TURBO_TEAM,env=TURBO_TEAM --no-cache",
"act": "act --secret-file .env.ci",
"db:migrate": "pnpm --filter=@hctv/db db:migrate",
+ "db:studio": "pnpm --filter=@hctv/db db:studio",
"ui:add": "pnpm --filter=@hctv/web ui:add",
"prisma": "pnpm --filter=@hctv/db prisma",
"r:rtmp": "docker compose -f dev/docker-compose.yml restart nginx-rtmp -t 0",
diff --git a/packages/db/package.json b/packages/db/package.json
index 39516e5..e770ed0 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -19,6 +19,7 @@
"db:migrate": "prisma migrate dev",
"db:deploy": "prisma migrate deploy",
"db:populate-verification": "tsx src/populateHackClubVerification.ts",
+ "db:studio": "prisma studio",
"build": "prisma generate && tsc --build",
"dev": "tsc --watch --preserveWatchOutput"
},
@@ -27,4 +28,4 @@
"tsx": "^4.7.1",
"typescript": "^5.8.2"
}
-}
+}
\ No newline at end of file