v3: added a chatbox
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled

This commit is contained in:
khannurien
2026-06-29 20:25:10 +00:00
parent f171027673
commit 6c4f410d9b
35 changed files with 1800 additions and 152 deletions

View File

@@ -2,6 +2,7 @@ import { randomBytes, scryptSync } from "node:crypto";
import { DatabaseSync, type SQLOutputValue } from "node:sqlite";
import {
type Category,
type ChatMessage,
type Comment,
Dump,
isRole,
@@ -304,6 +305,43 @@ export function commentRowToApi(row: CommentRow): Comment {
};
}
export interface ChatMessageRow {
id: string;
user_id: string;
body: string;
created_at: string;
updated_at: string | null;
author_username: string;
author_avatar_mime: string | null;
[key: string]: SQLOutputValue;
}
export function isChatMessageRow(obj: unknown): obj is ChatMessageRow {
return !!obj && typeof obj === "object" &&
"id" in obj && typeof obj.id === "string" &&
"user_id" in obj && typeof obj.user_id === "string" &&
"body" in obj && typeof obj.body === "string" &&
"created_at" in obj && typeof obj.created_at === "string" &&
"updated_at" in obj &&
(typeof obj.updated_at === "string" || obj.updated_at === null) &&
"author_username" in obj && typeof obj.author_username === "string" &&
"author_avatar_mime" in obj &&
(typeof obj.author_avatar_mime === "string" ||
obj.author_avatar_mime === null);
}
export function chatMessageRowToApi(row: ChatMessageRow): ChatMessage {
return {
id: row.id,
userId: row.user_id,
body: row.body,
createdAt: new Date(row.created_at),
updatedAt: row.updated_at ? new Date(row.updated_at) : undefined,
authorUsername: row.author_username,
authorAvatarMime: row.author_avatar_mime ?? undefined,
};
}
export interface PlaylistRow {
id: string;
user_id: string;

View File

@@ -309,6 +309,25 @@ export interface CreateCommentRequest {
parentId?: string;
}
/**
* Chat
*/
export interface ChatMessage {
id: string;
userId: string;
body: string;
createdAt: Date;
updatedAt?: Date;
authorUsername: string;
authorAvatarMime?: string;
}
/** Wire format — createdAt arrives as an ISO string over JSON. */
export type RawChatMessage = Omit<ChatMessage, "createdAt"> & {
createdAt: string;
};
export function isCreateCommentRequest(
obj: unknown,
): obj is CreateCommentRequest {
@@ -532,13 +551,40 @@ export interface CommentLikeRemoveMessage {
commentId: string;
}
export interface ChatSendMessage {
type: "chat_send";
body: string;
}
// Tells the server whether this client currently has the chatbox open, so that
// mention notifications can be skipped for users who are already reading chat.
export interface ChatFocusMessage {
type: "chat_focus";
open: boolean;
}
export interface ChatEditMessage {
type: "chat_edit";
id: string;
body: string;
}
export interface ChatDeleteMessage {
type: "chat_delete";
id: string;
}
export type ClientToServerMessage =
| PingMessage
| PongMessage
| VoteCastMessage
| VoteRemoveMessage
| CommentLikeCastMessage
| CommentLikeRemoveMessage;
| CommentLikeRemoveMessage
| ChatSendMessage
| ChatFocusMessage
| ChatEditMessage
| ChatDeleteMessage;
// ── Server → Client ──────────────────────────────────────────────────────────
@@ -669,6 +715,21 @@ export interface ForceLogoutMessage {
type: "force_logout";
}
export interface ChatMessageMessage {
type: "chat_message";
message: ChatMessage;
}
export interface ChatMessageUpdatedMessage {
type: "chat_message_updated";
message: ChatMessage;
}
export interface ChatMessageDeletedMessage {
type: "chat_message_deleted";
id: string;
}
export type ServerToClientMessage =
| PingMessage
| WelcomeMessage
@@ -690,7 +751,10 @@ export type ServerToClientMessage =
| CommentDeletedMessage
| NotificationCreatedMessage
| ErrorMessage
| ForceLogoutMessage;
| ForceLogoutMessage
| ChatMessageMessage
| ChatMessageUpdatedMessage
| ChatMessageDeletedMessage;
/**
* Follows
@@ -751,7 +815,7 @@ export interface DumpUpvotedData {
export interface UserMentionedData {
mentionerId: string;
mentionerUsername: string;
contextType: "comment" | "dump" | "playlist";
contextType: "comment" | "dump" | "playlist" | "chat";
contextId: string;
contextTitle: string;
dumpId?: string;