feat: add message history

This commit is contained in:
2025-07-02 13:16:47 +02:00
parent ed1fd6a18e
commit 9b8e83d2e2
6 changed files with 70 additions and 32 deletions

View File

@@ -5,8 +5,10 @@ import { readFile } from 'node:fs/promises';
import { lucia } from '@hctv/auth';
import { getCookie } from 'hono/cookie';
import { getPersonalChannel } from './utils/personalChannel.js';
import { prisma } from '@hctv/db';
import { getRedisConnection, prisma } from '@hctv/db';
const MESSAGE_HISTORY_SIZE = 15;
const MESSAGE_TTL = 60 * 60 * 24;
const threed = await readFile('./src/3d.txt', 'utf-8');
const app = new Hono();
@@ -54,6 +56,19 @@ app.get(
ws.raw.personalChannel = personalChannel;
}
const redis = getRedisConnection();
const channelKey = `chat:history:${username}`;
const messages = await redis.zrange(channelKey, 0, MESSAGE_HISTORY_SIZE - 1);
if (messages.length > 0) {
ws.send(
JSON.stringify({
type: 'history',
messages: messages.map((msg) => JSON.parse(msg)),
})
);
}
await prisma.streamInfo.update({
where: {
username,
@@ -98,19 +113,27 @@ app.get(
return;
}
if (msg.type === 'message') {
const msgObj = {
user: {
id: ws.user.id,
username: ws.personalChannel.name,
pfpUrl: ws.user.pfpUrl,
},
message: msg.message,
};
const msgStr = JSON.stringify(msgObj);
const redis = getRedisConnection();
const channelKey = `chat:history:${ws.targetUsername}`;
redis.zadd(channelKey, Date.now(), msgStr);
redis.zremrangebyrank(channelKey, 0, -MESSAGE_HISTORY_SIZE - 1);
redis.expire(channelKey, MESSAGE_TTL);
ws.wss.clients.forEach((c) => {
const client = c as ModifiedWebSocket;
if (client.readyState === client.OPEN && client.targetUsername === ws.targetUsername) {
c.send(
JSON.stringify({
user: {
id: ws.user.id,
username: ws.personalChannel.name,
pfpUrl: ws.user.pfpUrl,
},
message: msg.message,
})
);
c.send(msgStr);
}
});
}

View File

@@ -5,6 +5,7 @@ import { Send } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { useParams } from 'next/navigation';
import { Message } from './message';
export default function ChatPanel() {
const { username } = useParams();
@@ -28,7 +29,12 @@ export default function ChatPanel() {
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'ping' || data.type === 'pong' || !data.user) return;
if (data.type === 'ping' || data.type === 'pong') return;
if (data.type === 'history') {
const messages = data.messages as ChatMessage[];
setChatMessages((prev) => [...prev, ...messages, { message: 'Welcome to the chat!', type: 'systemMsg' }]);
return;
}
setChatMessages((prev) => [...prev, data]);
} catch (e) {
console.log('Received message confirmation:', event.data);
@@ -86,17 +92,7 @@ export default function ChatPanel() {
<div ref={scrollRef} className="flex-1 p-4 overflow-y-auto flex flex-col">
<div className="space-y-4 flex-1">
{chatMessages.map((msg, i) => (
<div key={i} className="flex space-x-2">
<div className="flex items-center gap-2">
<div className="font-bold shrink-0">{msg.user.username}</div>
</div>
<div
lang="en"
className="max-w-[calc(100%-4rem)] break-all whitespace-pre-wrap hyphens-auto"
>
{msg.message}
</div>
</div>
<Message key={i} user={msg.user} message={msg.message} type={msg.type} />
))}
</div>
</div>
@@ -122,13 +118,14 @@ export default function ChatPanel() {
);
}
interface User {
export interface ChatMessage {
user?: User;
message: string;
type: 'message' | 'systemMsg';
}
export interface User {
id: string;
username: string;
pfpUrl: string;
}
interface ChatMessage {
user: User;
message: string;
}

View File

@@ -0,0 +1,18 @@
import { cn } from "@/lib/utils";
import { ChatMessage } from "./ChatPanel";
export function Message({ user, message, type }: ChatMessage) {
return (
<div className="flex">
<div
lang="en"
className={cn("max-w-full break-all whitespace-pre-wrap hyphens-auto", type === "systemMsg" ? "text-muted-foreground" : "")}
>
<p>
<span className="font-bold mr-2">{user?.username}</span>
<span>{message}</span>
</p>
</div>
</div>
);
}

View File

@@ -4,7 +4,7 @@ import { cookies } from 'next/headers';
import { lucia } from '@hctv/auth';
import { validateRequest } from '@/lib/auth/validate';
import { redirect } from 'next/navigation';
import { getRedisConnection } from '../services/redis';
import { getRedisConnection } from '@hctv/db';
export async function logout() {
const { session } = await validateRequest();

View File

@@ -1,7 +1,7 @@
import { cookies } from "next/headers";
import { cache } from "react";
import { lucia } from '@hctv/auth';
import { getRedisConnection } from "../services/redis";
import { getRedisConnection } from "@hctv/db";
export const validateRequest = cache(async () => {
const sessionId = (await cookies()).get(lucia.sessionCookieName)?.value ?? null;

View File

@@ -1,5 +1,5 @@
import { prisma } from "@hctv/db";
import { getRedisConnection } from "../services/redis";
import { getRedisConnection } from "@hctv/db";
export default async function writeSessions() {
const sessions = await prisma.session.findMany();