fix loads of errors, make messages have the actual username added to

them aswell xd
This commit is contained in:
Soph :3 2026-01-05 07:59:23 +02:00
parent bf679f9ee0
commit 424fe4cf42
6 changed files with 157 additions and 76 deletions

View file

@ -14,13 +14,18 @@
import Button, { buttonVariants } from './ui/button/button.svelte'; import Button, { buttonVariants } from './ui/button/button.svelte';
import User from './extra/User.svelte'; import User from './extra/User.svelte';
import type { SessionValidationResult } from '$lib/server/auth'; import type { SessionValidationResult } from '$lib/server/auth';
import type { OverviewData } from '$lib';
let { let {
currentPage = $bindable<string | null>(), currentPage = $bindable<string | null>(),
data, data,
user, user,
...restProps ...restProps
}: { currentPage: string | null; data: Data; user: SessionValidationResult['user'] } = $props(); }: {
currentPage: string | null;
data: OverviewData;
user: SessionValidationResult['user'];
} = $props();
</script> </script>
<Sidebar.Root {...restProps}> <Sidebar.Root {...restProps}>
@ -48,7 +53,7 @@
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]"> <Dialog.Content class="sm:max-w-106.25">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Add a friend</Dialog.Title> <Dialog.Title>Add a friend</Dialog.Title>
<Dialog.Description> <Dialog.Description>
@ -132,7 +137,7 @@
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]"> <Dialog.Content class="sm:max-w-106.25">
<form method="POST" action="?/createGroup"> <form method="POST" action="?/createGroup">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Create a group</Dialog.Title> <Dialog.Title>Create a group</Dialog.Title>
@ -161,7 +166,7 @@
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]"> <Dialog.Content class="sm:max-w-106.25">
<form method="POST" action="?/joinServer"> <form method="POST" action="?/joinServer">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Join a server</Dialog.Title> <Dialog.Title>Join a server</Dialog.Title>
@ -185,7 +190,7 @@
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]"> <Dialog.Content class="sm:max-w-106.25">
<form method="POST" action="?/createServer"> <form method="POST" action="?/createServer">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Create a server</Dialog.Title> <Dialog.Title>Create a server</Dialog.Title>

View file

@ -23,6 +23,19 @@ export type OverviewUser = {
image: string; image: string;
dmId?: string; dmId?: string;
}; };
export interface Message {
id: string;
authorId: string;
content: string;
timestamp: number;
}
export interface ReturnMessage extends Message {
author: {
id: string;
name: string;
};
}
export type OverviewServer = { export type OverviewServer = {
id: string; id: string;

View file

@ -84,6 +84,7 @@ export const invite = sqliteTable('invite', {
.references(() => server.id), .references(() => server.id),
code: text('code').notNull() code: text('code').notNull()
}); });
export type Session = typeof session.$inferSelect; export type Session = typeof session.$inferSelect;
export type User = typeof user.$inferSelect; export type User = typeof user.$inferSelect;
export type Group = typeof group.$inferSelect; export type Group = typeof group.$inferSelect;

View file

@ -5,7 +5,7 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function formatTimestamp(dateString: string) { export function formatTimestamp(dateString: string | number | Date) {
const date = new Date(dateString); const date = new Date(dateString);
const now = new Date(); const now = new Date();

View file

@ -2,18 +2,11 @@ import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { db } from '$lib/server/db'; import { db } from '$lib/server/db';
import * as table from '$lib/server/db/schema'; import * as table from '$lib/server/db/schema';
import { DirectMessageID, GroupID, ServerID } from '$lib'; import { DirectMessageID, GroupID, ServerID, type Message, type ReturnMessage } from '$lib';
import { eq, or } from 'drizzle-orm'; import { eq, or } from 'drizzle-orm';
import { _sendToSubscribers } from '../../../../updates/+server'; import { _sendToSubscribers } from '../../../../updates/+server';
import { and } from 'drizzle-orm'; import { and } from 'drizzle-orm';
interface Message {
id: string;
authorId: string;
content: string;
timestamp: number;
}
export async function _findDm(member_one: string, member_two: string) { export async function _findDm(member_one: string, member_two: string) {
return await db return await db
.select() .select()
@ -72,14 +65,14 @@ export const GET: RequestHandler = async ({ params, locals }) => {
const g = (await db.select().from(table.group).where(eq(table.group.id, grp_srv_dm)))[0]; const g = (await db.select().from(table.group).where(eq(table.group.id, grp_srv_dm)))[0];
if (!g) return new Response('Group not found.', { status: 404 }); if (!g) return new Response('Group not found.', { status: 404 });
messages = g.messages ?? []; messages = g.messages as Message[];
} else if (ServerID.is(grp_srv_dm)) { } else if (ServerID.is(grp_srv_dm)) {
type = 'server'; type = 'server';
if (!channelId) return new Response('Missing channel ID.', { status: 400 }); if (!channelId) return new Response('Missing channel ID.', { status: 400 });
const c = (await db.select().from(table.channel).where(eq(table.channel.id, channelId)))[0]; const c = (await db.select().from(table.channel).where(eq(table.channel.id, channelId)))[0];
if (!c) return new Response('Channel not found.', { status: 404 }); if (!c) return new Response('Channel not found.', { status: 404 });
messages = c.messages; messages = c.messages as Message[];
} else if (DirectMessageID.is(grp_srv_dm)) { } else if (DirectMessageID.is(grp_srv_dm)) {
type = 'dms'; type = 'dms';
const dm = ( const dm = (
@ -88,13 +81,32 @@ export const GET: RequestHandler = async ({ params, locals }) => {
if (!dm) return new Response('DM not found.', { status: 404 }); if (!dm) return new Response('DM not found.', { status: 404 });
messages = dm.messages; messages = dm.messages as Message[];
} }
const messages_resolved_users: ReturnMessage[] = (
await Promise.all(
messages.map(async (z) => {
const author = await db.select().from(table.user).where(eq(table.user.id, z.authorId));
if (!author[0]) {
return;
}
return {
...z,
author: {
id: author[0].id,
name: author[0].username
}
};
})
)
).filter(Boolean) as ReturnMessage[];
return json({ return json({
type, type,
id: grp_srv_dm, id: grp_srv_dm,
messages messages: messages_resolved_users
}); });
}; };
@ -103,7 +115,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
return new Response('Unauthorized', { status: 401 }); return new Response('Unauthorized', { status: 401 });
} }
let { grp_srv_dm, channelId } = params; const { grp_srv_dm, channelId } = params;
if (!grp_srv_dm) return new Response('Missing Group/Server/DM id.', { status: 400 }); if (!grp_srv_dm) return new Response('Missing Group/Server/DM id.', { status: 400 });
const data = await request.json(); const data = await request.json();
const { content } = data; const { content } = data;
@ -113,7 +125,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
let messages: Message[] = []; let messages: Message[] = [];
let type = ''; let type = '';
const message = { const message: Message = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
authorId: locals.user.id, authorId: locals.user.id,
content, content,
@ -124,7 +136,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
type = 'group'; type = 'group';
const g = (await db.select().from(table.group).where(eq(table.group.id, grp_srv_dm)))[0]; const g = (await db.select().from(table.group).where(eq(table.group.id, grp_srv_dm)))[0];
if (!g) return new Response('Group not found.', { status: 404 }); if (!g) return new Response('Group not found.', { status: 404 });
messages = g.messages ?? []; messages = (g.messages as Message[]) ?? [];
messages.push(message); messages.push(message);
@ -134,7 +146,7 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!channelId) return new Response('Missing channel ID.', { status: 400 }); if (!channelId) return new Response('Missing channel ID.', { status: 400 });
const c = (await db.select().from(table.channel).where(eq(table.channel.id, channelId)))[0]; const c = (await db.select().from(table.channel).where(eq(table.channel.id, channelId)))[0];
if (!c) return new Response('Channel not found.', { status: 404 }); if (!c) return new Response('Channel not found.', { status: 404 });
messages = c.messages ?? []; messages = (c.messages as Message[]) ?? [];
messages.push(message); messages.push(message);
@ -147,17 +159,37 @@ export const POST: RequestHandler = async ({ params, request, locals }) => {
if (!dm) return new Response('DM not found.', { status: 404 }); if (!dm) return new Response('DM not found.', { status: 404 });
messages = dm.messages ?? []; messages = (dm.messages as Message[]) ?? [];
messages.push(message); messages.push(message);
await db.update(table.directMessage).set({ messages }).where(eq(table.directMessage.id, dm.id)); await db.update(table.directMessage).set({ messages }).where(eq(table.directMessage.id, dm.id));
_sendToSubscribers(dm.id, { type: 'message', id: dm.id, message }); _sendToSubscribers(dm.id, {
type: 'message',
id: dm.id,
message: {
...message,
author: {
id: locals.user.id,
name: locals.user.username
}
}
});
return json({ type, id: dm.id, messages }); return json({ type, id: dm.id, messages });
} }
_sendToSubscribers(grp_srv_dm, { type: 'message', id: grp_srv_dm, message }); _sendToSubscribers(grp_srv_dm, {
type: 'message',
id: grp_srv_dm,
message: {
...message,
author: {
id: locals.user.id,
name: locals.user.username
}
}
});
return json({ type, id: grp_srv_dm, messages }); return json({ type, id: grp_srv_dm, messages });
}; };

View file

@ -11,7 +11,8 @@
type OverviewUser, type OverviewUser,
type OverviewGroup, type OverviewGroup,
type OverviewServer, type OverviewServer,
type UserWithStatus type UserWithStatus,
type ReturnMessage
} from '$lib'; } from '$lib';
import type { PageServerData } from './$types'; import type { PageServerData } from './$types';
import AppSidebar from '$lib/components/app-sidebar.svelte'; import AppSidebar from '$lib/components/app-sidebar.svelte';
@ -22,17 +23,19 @@
import { formatTimestamp } from '$lib/utils'; import { formatTimestamp } from '$lib/utils';
import Input from '$lib/components/ui/input/input.svelte'; import Input from '$lib/components/ui/input/input.svelte';
import { Button } from '$lib/components/ui/button'; import { Button } from '$lib/components/ui/button';
import { SendHorizontal } from '@lucide/svelte';
let errorOpen = $state(true); let errorOpen = $state(true);
let { form, data }: { form: ActionData; data: PageServerData } = $props(); let { form, data }: { form: ActionData; data: PageServerData } = $props();
let currentPageID: (UserId | GroupId | ServerId) | null = $state(null); let currentPageID: (UserId | GroupId | ServerId) | null = $state(null);
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state(); let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
let messages = $state([]); let messages: ReturnMessage[] = $state([]);
let inputValue = $state(); let inputValue = $state();
let sessionId: string | undefined = $state(); let sessionId: string | undefined = $state();
let previousSubscription: typeof currentPageID = $state(null); let previousSubscription: UserId | GroupId | ServerId | null = $state(null);
const overview_data: OverviewData = $state({ const overview_data: OverviewData = $state({
friends: [], friends: [],
@ -59,21 +62,23 @@
if (ServerID.is(currentPageID)) return; if (ServerID.is(currentPageID)) return;
async function getMessages() { async function getMessages() {
const req = await fetch('/api/messages/' + (currentPage!.dmId || currentPageID)); const targetId = currentPage && 'dmId' in currentPage ? currentPage.dmId : currentPageID;
const req = await fetch('/api/messages/' + targetId);
const data = await req.json(); const data = await req.json();
messages = data.messages; messages = data.messages;
if (previousSubscription && previousSubscription != (currentPage!.dmId || currentPageID)) { if (previousSubscription && previousSubscription != targetId) {
await fetch('/api/updates/' + sessionId, { await fetch('/api/updates/' + sessionId, {
body: JSON.stringify({ subscribeTo: previousSubscription }), body: JSON.stringify({ subscribeTo: previousSubscription }),
method: 'DELETE' method: 'DELETE'
}); });
} }
if (previousSubscription != (currentPage!.dmId || currentPageID)) { if (previousSubscription != targetId) {
await fetch('/api/updates/' + sessionId, { await fetch('/api/updates/' + sessionId, {
body: JSON.stringify({ subscribeTo: currentPage!.dmId || currentPageID }), body: JSON.stringify({ subscribeTo: targetId }),
method: 'POST' method: 'POST'
}); });
} }
@ -135,6 +140,7 @@
sse.addEventListener('message', (e) => { sse.addEventListener('message', (e) => {
const json = JSON.parse(e.data) as const json = JSON.parse(e.data) as
| { type: 'connected'; sessionId: string } | { type: 'connected'; sessionId: string }
| { type: 'message'; message: ReturnMessage }
| { type: 'status'; id: string; status: 1 | 2 | 3 }; | { type: 'status'; id: string; status: 1 | 2 | 3 };
if (json.type == 'connected') { if (json.type == 'connected') {
@ -175,7 +181,7 @@
<Sidebar.Provider> <Sidebar.Provider>
<AppSidebar bind:currentPage={currentPageID} user={data.user} data={overview_data} /> <AppSidebar bind:currentPage={currentPageID} user={data.user} data={overview_data} />
<Sidebar.Inset> <Sidebar.Inset class="h-svh">
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4"> <header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
<Sidebar.Trigger class="-ms-1" /> <Sidebar.Trigger class="-ms-1" />
{#if currentPageID && currentPage} {#if currentPageID && currentPage}
@ -206,56 +212,80 @@
{/if} {/if}
{/if} {/if}
</header> </header>
<div class="h-min shrink overflow-scroll">
{#each messages as message, i (message.id)}
{#if i === 0 || messages[i - 1].authorId !== message.authorId}
<div class="flex gap-2 px-4 py-2">
<img
src={'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + message.author.name}
alt={message.author.name}
class="h-6 w-6 rounded-full"
/>
{#each messages as message, i (message.id)} <div class="flex flex-col">
{#if i === 0 || messages[i - 1].authorId !== message.authorId} <div class="flex items-baseline gap-2">
<div class="flex gap-2 px-4 py-2"> <span class="font-semibold">{message.author.name}</span>
<img <span class="text-xs text-gray-400">
src={'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + message.authorId} {formatTimestamp(message.timestamp)}
alt={message.authorId} </span>
class="h-6 w-6 rounded-full" </div>
/>
<div class="flex flex-col"> <div class="whitespace-pre-wrap">{message.content}</div>
<div class="flex items-baseline gap-2">
<span class="font-semibold">{message.authorId}</span>
<span class="text-xs text-gray-400">
{formatTimestamp(message.timestamp)}
</span>
</div> </div>
</div>
{:else}
<div class="ml-8 flex gap-8 px-4 py-1">
<div class="whitespace-pre-wrap">{message.content}</div> <div class="whitespace-pre-wrap">{message.content}</div>
</div> </div>
</div> {/if}
{:else} {/each}
<div class="ml-8 flex gap-8 px-4 py-1"> </div>
<div class="whitespace-pre-wrap">{message.content}</div>
</div>
{/if}
{/each}
{#if currentPageID && currentPage} {#if currentPageID && currentPage}
<Input <div class="flex shrink-0 gap-2 border-t p-2">
bind:value={inputValue} <Input
placeholder="input box for messages (ignore how ugly it is right now please)" bind:value={inputValue}
></Input> onkeydown={async (e) => {
<Button if (e.key == 'Enter') {
onclick={async () => { let message = inputValue;
const req = await fetch('/api/messages/' + (currentPage!.dmId || currentPageID), { const targetId =
method: 'POST', currentPage && 'dmId' in currentPage ? currentPage.dmId : currentPageID;
headers: { inputValue = '';
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: inputValue
})
});
if (!req.ok) { const req = await fetch('/api/messages/' + targetId, {
console.error('Failed to send message'); method: 'POST',
} headers: { 'Content-Type': 'application/json' },
}}>send</Button body: JSON.stringify({ content: message })
> });
if (!req.ok) {
console.error('Failed to send message');
}
}
}}
placeholder="input box for messages (ignore how ugly it is right now please)"
class="flex-1"
/>
<Button
onclick={async () => {
let message = inputValue;
const targetId =
currentPage && 'dmId' in currentPage ? currentPage.dmId : currentPageID;
inputValue = '';
const req = await fetch('/api/messages/' + targetId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: message })
});
if (!req.ok) {
console.error('Failed to send message');
}
}}
>
<SendHorizontal></SendHorizontal>
</Button>
</div>
{/if} {/if}
</Sidebar.Inset> </Sidebar.Inset>
</Sidebar.Provider> </Sidebar.Provider>