username update, status message update, status overwriting, global
status in client
This commit is contained in:
parent
9ffb3cf283
commit
d9f5919b60
11 changed files with 235 additions and 79 deletions
|
|
@ -14,7 +14,8 @@
|
||||||
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';
|
import { Status, statuses, type OverviewData, type OverviewUser } from '$lib';
|
||||||
|
import Label from './ui/label/label.svelte';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
currentPage = $bindable<string | null>(),
|
currentPage = $bindable<string | null>(),
|
||||||
|
|
@ -259,6 +260,7 @@
|
||||||
subPage = null;
|
subPage = null;
|
||||||
}}
|
}}
|
||||||
user={friend}
|
user={friend}
|
||||||
|
crown={false}
|
||||||
></User>
|
></User>
|
||||||
</Sidebar.MenuSubButton>
|
</Sidebar.MenuSubButton>
|
||||||
</Sidebar.MenuSubItem>
|
</Sidebar.MenuSubItem>
|
||||||
|
|
@ -349,5 +351,66 @@
|
||||||
</Sidebar.Menu>
|
</Sidebar.Menu>
|
||||||
</Sidebar.Group>
|
</Sidebar.Group>
|
||||||
</Sidebar.Content>
|
</Sidebar.Content>
|
||||||
|
<Sidebar.Footer class="border-t-2 p-2">
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<User user={user as unknown as OverviewUser} crown={false} />
|
||||||
|
</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content>
|
||||||
|
<form method="POST" action="?/updateProfile">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>Edit profile</Dialog.Title>
|
||||||
|
<Dialog.Description>Update how others see you.</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<Label for="userName">Username</Label>
|
||||||
|
<Input
|
||||||
|
id="userName"
|
||||||
|
name="userName"
|
||||||
|
placeholder="Your name"
|
||||||
|
value={user?.username}
|
||||||
|
required
|
||||||
|
minlength={2}
|
||||||
|
maxlength={32}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Presence -->
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="status">Status</Label>
|
||||||
|
<select id="status" name="status" class="input" required>
|
||||||
|
<option value={Status.ONLINE} selected={user?.statusOverwrite === Status.ONLINE}
|
||||||
|
>Online</option
|
||||||
|
>
|
||||||
|
<option value={Status.DND} selected={user?.statusOverwrite === Status.DND}
|
||||||
|
>Do Not Disturb</option
|
||||||
|
>
|
||||||
|
<option value={Status.OFFLINE} selected={user?.statusOverwrite === Status.OFFLINE}
|
||||||
|
>Offline</option
|
||||||
|
>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-1">
|
||||||
|
<Label for="statusMessage">Status message</Label>
|
||||||
|
<Input
|
||||||
|
id="statusMessage"
|
||||||
|
name="statusMessage"
|
||||||
|
placeholder="What's going on?"
|
||||||
|
value={user?.statusMessage ?? ''}
|
||||||
|
maxlength={64}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
|
||||||
|
<Button type="submit">Save</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
</Sidebar.Footer>
|
||||||
<Sidebar.Rail />
|
<Sidebar.Rail />
|
||||||
</Sidebar.Root>
|
</Sidebar.Root>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, type UserWithStatus } from '$lib';
|
import { Status, statuses, type OverviewUser } from '$lib';
|
||||||
import Crown from '@lucide/svelte/icons/crown';
|
import Crown from '@lucide/svelte/icons/crown';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onclick,
|
onclick,
|
||||||
user,
|
user,
|
||||||
crown
|
crown
|
||||||
}: { crown: boolean; onclick?: (e: MouseEvent) => void; user: UserWithStatus } = $props();
|
}: { crown: boolean; onclick?: (e: MouseEvent) => void; user: OverviewUser } = $props();
|
||||||
|
|
||||||
|
let status: Status | undefined = $derived(statuses.get(user.id));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
|
|
@ -16,20 +18,23 @@
|
||||||
alt={user.username}
|
alt={user.username}
|
||||||
class="size-6 rounded-full"
|
class="size-6 rounded-full"
|
||||||
/>
|
/>
|
||||||
|
{#if status}
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if user.status === Status.OFFLINE}
|
{#if status.status === Status.OFFLINE}
|
||||||
<span
|
<span
|
||||||
class="absolute end-0 bottom-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"
|
class="absolute end-0 bottom-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"
|
||||||
></span>
|
></span>
|
||||||
{:else if user.status === Status.DND}
|
{:else if status.status === Status.DND}
|
||||||
<span class="absolute end-0 bottom-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"
|
<span
|
||||||
|
class="absolute end-0 bottom-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"
|
||||||
></span>
|
></span>
|
||||||
{:else if user.status === Status.ONLINE}
|
{:else if status.status === Status.ONLINE}
|
||||||
<span
|
<span
|
||||||
class="absolute end-0 bottom-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"
|
class="absolute end-0 bottom-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"
|
||||||
></span>
|
></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
|
|
@ -46,8 +51,10 @@
|
||||||
<Crown></Crown>
|
<Crown></Crown>
|
||||||
{/if}
|
{/if}
|
||||||
</a>
|
</a>
|
||||||
|
{#if status}
|
||||||
<div class="pl-2 text-xs text-gray-400 italic">
|
<div class="pl-2 text-xs text-gray-400 italic">
|
||||||
{user.statusMessage}
|
{status.statusMessage}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
type OverviewData,
|
type OverviewData,
|
||||||
type OverviewGroup,
|
type OverviewGroup,
|
||||||
type OverviewServer,
|
type OverviewServer,
|
||||||
type UserWithStatus
|
type OverviewUser
|
||||||
} from '$lib';
|
} from '$lib';
|
||||||
import Button from './ui/button/button.svelte';
|
import Button from './ui/button/button.svelte';
|
||||||
import Input from './ui/input/input.svelte';
|
import Input from './ui/input/input.svelte';
|
||||||
|
|
@ -20,14 +20,14 @@
|
||||||
// Props for the member sidebar.
|
// Props for the member sidebar.
|
||||||
let {
|
let {
|
||||||
open = $bindable(true),
|
open = $bindable(true),
|
||||||
members = $bindable<UserWithStatus[]>([]),
|
members = $bindable<OverviewUser[]>([]),
|
||||||
user,
|
user,
|
||||||
data,
|
data,
|
||||||
currentEntity,
|
currentEntity,
|
||||||
currentEntityId = $bindable<string | null>(null)
|
currentEntityId = $bindable<string | null>(null)
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
members: UserWithStatus[];
|
members: OverviewUser[];
|
||||||
data: OverviewData;
|
data: OverviewData;
|
||||||
user: SessionValidationResult['user'];
|
user: SessionValidationResult['user'];
|
||||||
currentEntity: OverviewGroup | OverviewServer;
|
currentEntity: OverviewGroup | OverviewServer;
|
||||||
|
|
@ -49,13 +49,13 @@
|
||||||
{#if user && currentEntityId}
|
{#if user && currentEntityId}
|
||||||
<Dialog.Root>
|
<Dialog.Root>
|
||||||
<Dialog.Trigger><Button variant="outline"><Cog></Cog></Button></Dialog.Trigger>
|
<Dialog.Trigger><Button variant="outline"><Cog></Cog></Button></Dialog.Trigger>
|
||||||
<Dialog.Content class="sm:max-w-[425px]">
|
<Dialog.Content class="sm:max-w-106.25">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Group Settings</Dialog.Title>
|
<Dialog.Title>Group Settings</Dialog.Title>
|
||||||
<Dialog.Description>Configure your group settings here.</Dialog.Description>
|
<Dialog.Description>Configure your group settings here.</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<Tabs.Root value="users" class="w-[400px]">
|
<Tabs.Root value="users" class="w-100">
|
||||||
<Tabs.List class="grid w-full grid-cols-2">
|
<Tabs.List class="grid w-full grid-cols-2">
|
||||||
<Tabs.Trigger value="users">User Permissions</Tabs.Trigger>
|
<Tabs.Trigger value="users">User Permissions</Tabs.Trigger>
|
||||||
{#if user.id == currentEntity.ownerId}
|
{#if user.id == currentEntity.ownerId}
|
||||||
|
|
@ -194,9 +194,7 @@
|
||||||
{#each members as member (member.id)}
|
{#each members as member (member.id)}
|
||||||
<Sidebar.MenuItem>
|
<Sidebar.MenuItem>
|
||||||
<Sidebar.MenuButton>
|
<Sidebar.MenuButton>
|
||||||
{#snippet child({ props })}
|
|
||||||
<User user={member} crown={member.id == currentEntity.ownerId} />
|
<User user={member} crown={member.id == currentEntity.ownerId} />
|
||||||
{/snippet}
|
|
||||||
</Sidebar.MenuButton>
|
</Sidebar.MenuButton>
|
||||||
</Sidebar.MenuItem>
|
</Sidebar.MenuItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { definePrefix, type Puuid } from './puuid';
|
import { definePrefix, type Puuid } from './puuid';
|
||||||
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
|
||||||
export const UserID = definePrefix('user');
|
export const UserID = definePrefix('user');
|
||||||
export const GroupID = definePrefix('group');
|
export const GroupID = definePrefix('group');
|
||||||
|
|
@ -14,6 +15,12 @@ export type ServerId = Puuid<'srv'>;
|
||||||
export type DirectMessageId = Puuid<'dmid'>;
|
export type DirectMessageId = Puuid<'dmid'>;
|
||||||
export type ChannelId = Puuid<'ch'>;
|
export type ChannelId = Puuid<'ch'>;
|
||||||
|
|
||||||
|
export interface Status {
|
||||||
|
statusMessage: string;
|
||||||
|
status: 1 | 2 | 3;
|
||||||
|
}
|
||||||
|
export const statuses: Map<string, Status> = new SvelteMap();
|
||||||
|
|
||||||
export const Status: Record<string, 1 | 2 | 3> = {
|
export const Status: Record<string, 1 | 2 | 3> = {
|
||||||
OFFLINE: 1,
|
OFFLINE: 1,
|
||||||
DND: 2,
|
DND: 2,
|
||||||
|
|
@ -65,13 +72,8 @@ export type OverviewGroup = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface UserWithStatus extends OverviewUser {
|
|
||||||
status: 1 | 2 | 3;
|
|
||||||
statusMessage: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OverviewData {
|
export interface OverviewData {
|
||||||
friends: UserWithStatus[];
|
friends: OverviewUser[];
|
||||||
groups: OverviewGroup[];
|
groups: OverviewGroup[];
|
||||||
servers: OverviewServer[];
|
servers: OverviewServer[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import type { RequestEvent } from '@sveltejs/kit';
|
||||||
import { eq, inArray, or } from 'drizzle-orm';
|
import { eq, inArray, or } from 'drizzle-orm';
|
||||||
import { sha256 } from '@oslojs/crypto/sha2';
|
import { sha256 } from '@oslojs/crypto/sha2';
|
||||||
import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding';
|
import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding';
|
||||||
import { db } from '$lib/server/db';
|
import { db, kvStore } from '$lib/server/db';
|
||||||
import * as table from '$lib/server/db/schema';
|
import * as table from '$lib/server/db/schema';
|
||||||
import { _findDmId } from '../../routes/api/messages/[[grp_srv_dm]]/[[channelId]]/[[channelId]]/+server';
|
import { _findDmId } from '../../routes/api/messages/[[grp_srv_dm]]/[[channelId]]/[[channelId]]/+server';
|
||||||
|
|
||||||
|
|
@ -159,7 +159,8 @@ export async function validateSessionToken(token: string) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
friendRequests
|
friendRequests,
|
||||||
|
statusMessage: kvStore.get('user-' + user.id + '-message')
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,8 @@ const sqlite = new Database('database.db');
|
||||||
export const db = drizzle(sqlite);
|
export const db = drizzle(sqlite);
|
||||||
|
|
||||||
export const kvStore = new BunSqliteKeyValue('./kvStore.db');
|
export const kvStore = new BunSqliteKeyValue('./kvStore.db');
|
||||||
|
|
||||||
|
console.log('nuking kvstore');
|
||||||
|
kvStore.getItems()?.forEach((z) => {
|
||||||
|
if (z.key.endsWith('state')) kvStore.set(z.key, 1);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { db, kvStore } from '$lib/server/db';
|
import { db, kvStore } from '$lib/server/db';
|
||||||
import * as table from '$lib/server/db/schema';
|
import * as table from '$lib/server/db/schema';
|
||||||
import { eq, inArray } from 'drizzle-orm';
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
import { GroupID, ServerID } from '$lib';
|
import { GroupID, ServerID, Status } from '$lib';
|
||||||
import { error, json } from '@sveltejs/kit';
|
import { error, json } from '@sveltejs/kit';
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
|
|
||||||
|
|
@ -27,8 +27,15 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||||
return json({
|
return json({
|
||||||
members: members.map((member) => ({
|
members: members.map((member) => ({
|
||||||
id: member.id,
|
id: member.id,
|
||||||
status: kvStore.get('user-' + member.id + '-state'),
|
status:
|
||||||
statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : 'not vibing',
|
member.statusOverwrite == Status.OFFLINE
|
||||||
|
? Status.OFFLINE
|
||||||
|
: kvStore.get('user-' + member.id + '-state'),
|
||||||
|
statusMessage:
|
||||||
|
kvStore.get('user-' + member.id + '-state') != Status.OFFLINE &&
|
||||||
|
member.statusOverwrite != Status.OFFLINE
|
||||||
|
? kvStore.get('user-' + member.id + '-message')
|
||||||
|
: '',
|
||||||
username: member.username,
|
username: member.username,
|
||||||
image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}`
|
image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}`
|
||||||
}))
|
}))
|
||||||
|
|
@ -50,8 +57,15 @@ export const GET: RequestHandler = async ({ params }) => {
|
||||||
return json({
|
return json({
|
||||||
members: members.map((member) => ({
|
members: members.map((member) => ({
|
||||||
id: member.id,
|
id: member.id,
|
||||||
status: kvStore.get('user-' + member.id + '-state'),
|
status:
|
||||||
statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : 'not vibing',
|
member.statusOverwrite == Status.OFFLINE
|
||||||
|
? Status.OFFLINE
|
||||||
|
: kvStore.get('user-' + member.id + '-state'),
|
||||||
|
statusMessage:
|
||||||
|
kvStore.get('user-' + member.id + '-state') != Status.OFFLINE &&
|
||||||
|
member.statusOverwrite != Status.OFFLINE
|
||||||
|
? kvStore.get('user-' + member.id + '-message')
|
||||||
|
: '',
|
||||||
username: member.username,
|
username: member.username,
|
||||||
image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}`
|
image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}`
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,32 @@
|
||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import type { RequestHandler } from './$types';
|
import type { RequestHandler } from './$types';
|
||||||
import { kvStore } from '$lib/server/db';
|
import { db, kvStore } from '$lib/server/db';
|
||||||
import { Status } from '$lib';
|
import { Status } from '$lib';
|
||||||
|
import * as table from '$lib/server/db/schema';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
|
||||||
export const GET: RequestHandler = async ({ params }) => {
|
export const GET: RequestHandler = async ({ params }) => {
|
||||||
const { userId } = params;
|
const { userId } = params;
|
||||||
|
|
||||||
|
const user = await db
|
||||||
|
.select({
|
||||||
|
statusOverwrite: table.user.statusOverwrite
|
||||||
|
})
|
||||||
|
.from(table.user)
|
||||||
|
.where(eq(table.user.id, userId));
|
||||||
|
if (!user || user.length == 0) {
|
||||||
|
return new Response('User missing', { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
let current = kvStore.get('user-' + userId + '-state');
|
||||||
|
|
||||||
|
if (user[0].statusOverwrite == Status.OFFLINE) {
|
||||||
|
current = Status.OFFLINE;
|
||||||
|
}
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
userId,
|
userId,
|
||||||
status: kvStore.get('user-' + userId + '-state'),
|
status: current,
|
||||||
statusMessage:
|
statusMessage: current != Status.OFFLINE ? kvStore.get('user-' + userId + '-message') : ''
|
||||||
kvStore.get('user-' + userId + '-state') != Status.OFFLINE
|
|
||||||
? Math.random() > 0.5
|
|
||||||
? 'vibing 🟢'
|
|
||||||
: 'not vibing'
|
|
||||||
: ''
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export function _sendToSubscribers(id: string, payload: unknown) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function _sendToUser(userId: string, payload: unknown) {
|
export function _sendToUser(userId: string, payload: unknown) {
|
||||||
for (const [_, client] of _clients) {
|
for (const [, client] of _clients) {
|
||||||
if (client.userId == userId) {
|
if (client.userId == userId) {
|
||||||
client.controller.enqueue(`data: ${JSON.stringify(payload)}\n\n`);
|
client.controller.enqueue(`data: ${JSON.stringify(payload)}\n\n`);
|
||||||
}
|
}
|
||||||
|
|
@ -59,10 +59,15 @@ export async function GET({ locals, request }) {
|
||||||
|
|
||||||
if (overwrite === Status.DND) {
|
if (overwrite === Status.DND) {
|
||||||
kvStore.set(`user-${userId}-state`, Status.DND);
|
kvStore.set(`user-${userId}-state`, Status.DND);
|
||||||
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.DND });
|
_sendToSubscribers(userId, {
|
||||||
} else {
|
type: 'status',
|
||||||
kvStore.set(`user-${userId}-state`, Status.ONLINE);
|
id: userId,
|
||||||
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.ONLINE });
|
status: Status.DND,
|
||||||
|
statusMessage:
|
||||||
|
kvStore.get('user-' + userId + '-state') != Status.OFFLINE
|
||||||
|
? kvStore.get('user-' + userId + '-message')
|
||||||
|
: ''
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
request.signal.addEventListener('abort', () => {
|
request.signal.addEventListener('abort', () => {
|
||||||
|
|
@ -74,7 +79,12 @@ export async function GET({ locals, request }) {
|
||||||
if (overwrite === Status.OFFLINE) return;
|
if (overwrite === Status.OFFLINE) return;
|
||||||
|
|
||||||
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
||||||
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.OFFLINE });
|
_sendToSubscribers(userId, {
|
||||||
|
type: 'status',
|
||||||
|
id: userId,
|
||||||
|
status: Status.OFFLINE,
|
||||||
|
statusMessage: ''
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
|
|
@ -85,7 +95,12 @@ export async function GET({ locals, request }) {
|
||||||
if (overwrite === Status.OFFLINE) return;
|
if (overwrite === Status.OFFLINE) return;
|
||||||
|
|
||||||
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
kvStore.set(`user-${userId}-state`, Status.OFFLINE);
|
||||||
_sendToSubscribers(userId, { type: 'status', id: userId, status: Status.OFFLINE });
|
_sendToSubscribers(userId, {
|
||||||
|
type: 'status',
|
||||||
|
id: userId,
|
||||||
|
status: Status.OFFLINE,
|
||||||
|
statusMessage: ''
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import { getRequestEvent } from '$app/server';
|
import { getRequestEvent } from '$app/server';
|
||||||
import type { Actions, PageServerLoad } from './$types';
|
import type { Actions, PageServerLoad } from './$types';
|
||||||
import { db } from '$lib/server/db';
|
import { db, kvStore } from '$lib/server/db';
|
||||||
import * as table from '$lib/server/db/schema';
|
import * as table from '$lib/server/db/schema';
|
||||||
import { DirectMessageID, FriendRequestID, GroupID, ServerID } from '$lib';
|
import { DirectMessageID, FriendRequestID, GroupID, ServerID } from '$lib';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { and } from 'drizzle-orm';
|
import { and } from 'drizzle-orm';
|
||||||
import { type User } from '$lib/server/db/schema';
|
import { type User } from '$lib/server/db/schema';
|
||||||
import { _sendToSubscribers, _sendToUser } from '../api/updates/+server';
|
import { _sendToSubscribers, _sendToUser } from '../api/updates/+server';
|
||||||
import { _findDmId } from '../api/messages/[[grp_srv_dm]]/[[channelId]]/[[channelId]]/+server';
|
import { validateUsername } from '$lib/server/auth';
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const user = requireLogin();
|
const user = requireLogin();
|
||||||
return { user };
|
return { user };
|
||||||
|
|
@ -196,6 +197,41 @@ export const actions = {
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
|
updateProfile: async ({ request, locals }) => {
|
||||||
|
const user = locals.user;
|
||||||
|
if (!user) return fail(401);
|
||||||
|
|
||||||
|
const data = await request.formData();
|
||||||
|
|
||||||
|
const userName = data.get('userName')?.toString().trim();
|
||||||
|
const status = data.has('status') ? +data.get('status')! : undefined;
|
||||||
|
const statusMessage = data.get('statusMessage')?.toString().trim() || undefined;
|
||||||
|
|
||||||
|
if (!validateUsername(userName)) {
|
||||||
|
return fail(400, { message: 'Invalid display name' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != 1 && status != 2 && status != 3) {
|
||||||
|
return fail(400, { message: 'Invalid status' });
|
||||||
|
}
|
||||||
|
|
||||||
|
kvStore.set('user-' + user.id + '-message', statusMessage);
|
||||||
|
await db
|
||||||
|
.update(table.user)
|
||||||
|
.set({
|
||||||
|
username: userName,
|
||||||
|
statusOverwrite: status
|
||||||
|
})
|
||||||
|
.where(eq(table.user.id, user.id));
|
||||||
|
|
||||||
|
if (statusMessage || status != user.statusOverwrite)
|
||||||
|
_sendToSubscribers(user.id, { type: 'status', id: user.id, status: status, statusMessage });
|
||||||
|
|
||||||
|
if (userName != user.username)
|
||||||
|
_sendToSubscribers(user.id, { type: 'username', status: 'name-changed' });
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
createGroup: async ({ request, locals }) => {
|
createGroup: async ({ request, locals }) => {
|
||||||
const data = await request.formData();
|
const data = await request.formData();
|
||||||
const members = data.getAll('member').map((z) => z.toString());
|
const members = data.getAll('member').map((z) => z.toString());
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
type OverviewUser,
|
type OverviewUser,
|
||||||
type OverviewGroup,
|
type OverviewGroup,
|
||||||
type OverviewServer,
|
type OverviewServer,
|
||||||
type UserWithStatus,
|
|
||||||
type ReturnMessage,
|
type ReturnMessage,
|
||||||
type Channel,
|
type Channel,
|
||||||
type ChannelId,
|
type ChannelId,
|
||||||
ChannelID
|
ChannelID,
|
||||||
|
statuses
|
||||||
} 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';
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
let sse: EventSource | undefined;
|
let sse: EventSource | undefined;
|
||||||
let messagesElement: HTMLDivElement | undefined = $state();
|
let messagesElement: HTMLDivElement | undefined = $state();
|
||||||
let isMembersTabOpen = $state(true);
|
let isMembersTabOpen = $state(true);
|
||||||
let members: UserWithStatus[] = $state([]);
|
let members: OverviewUser[] = $state([]);
|
||||||
|
|
||||||
let messages: ReturnMessage[] = $state([]);
|
let messages: ReturnMessage[] = $state([]);
|
||||||
let inputValue = $state();
|
let inputValue = $state();
|
||||||
|
|
@ -88,11 +88,14 @@
|
||||||
|
|
||||||
const status = await res.json();
|
const status = await res.json();
|
||||||
|
|
||||||
|
statuses.set(friend.id, {
|
||||||
|
status: status.status,
|
||||||
|
statusMessage: status.statusMessage
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: UserID.parse(friend.id),
|
id: UserID.parse(friend.id),
|
||||||
username: friend.username,
|
username: friend.username,
|
||||||
status: status.status,
|
|
||||||
statusMessage: status.statusMessage,
|
|
||||||
dmId: friend.dmId,
|
dmId: friend.dmId,
|
||||||
image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + friend.username
|
image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + friend.username
|
||||||
};
|
};
|
||||||
|
|
@ -198,9 +201,10 @@
|
||||||
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: 'message'; message: ReturnMessage }
|
||||||
| { type: 'status'; id: string; status: 1 | 2 | 3 }
|
| { type: 'status'; id: string; status: 1 | 2 | 3; statusMessage: string }
|
||||||
| { type: 'friends'; status: string }
|
| { type: 'friends'; status: string }
|
||||||
| { type: 'group'; status: string }
|
| { type: 'group'; status: string }
|
||||||
|
| { type: 'username'; status: string }
|
||||||
| { type: 'server'; status: string };
|
| { type: 'server'; status: string };
|
||||||
if (json.type == 'friends') {
|
if (json.type == 'friends') {
|
||||||
toast('Invalidation from friends updates, recieved ' + json.status);
|
toast('Invalidation from friends updates, recieved ' + json.status);
|
||||||
|
|
@ -216,25 +220,29 @@
|
||||||
toast('Invalidation from group updates, recieved ' + json.status);
|
toast('Invalidation from group updates, recieved ' + json.status);
|
||||||
if (json.status == 'removed-from-group' && GroupID.is(currentPageID)) {
|
if (json.status == 'removed-from-group' && GroupID.is(currentPageID)) {
|
||||||
currentPageID = null;
|
currentPageID = null;
|
||||||
currentPage = null;
|
currentPage = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
await invalidateAll();
|
await invalidateAll();
|
||||||
|
|
||||||
await fill_overview_data();
|
await fill_overview_data();
|
||||||
}
|
}
|
||||||
|
if (json.type == 'username') {
|
||||||
|
toast('Invalidation from username updates, recieved ' + json.status);
|
||||||
|
await invalidateAll();
|
||||||
|
|
||||||
|
await fill_overview_data();
|
||||||
|
}
|
||||||
if (json.type == 'connected') {
|
if (json.type == 'connected') {
|
||||||
console.log('SSE connected. We are sessionID ' + json.sessionId);
|
console.log('SSE connected. We are sessionID ' + json.sessionId);
|
||||||
sessionId = json.sessionId;
|
sessionId = json.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.type == 'status') {
|
if (json.type == 'status') {
|
||||||
//@TODO update everywhere where user is used
|
statuses.set(json.id, {
|
||||||
const friend = overview_data.friends.find((z) => z.id == json.id);
|
status: json.status,
|
||||||
|
statusMessage: json.statusMessage
|
||||||
if (friend) {
|
});
|
||||||
friend.status = json.status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (json.type == 'message') {
|
if (json.type == 'message') {
|
||||||
|
|
@ -294,18 +302,12 @@
|
||||||
<h1>{server!.name} - Server Info</h1>
|
<h1>{server!.name} - Server Info</h1>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if UserID.is(currentPageID)}
|
{:else if UserID.is(currentPageID)}
|
||||||
{@const friend = currentPage as UserWithStatus}
|
{@const friend = currentPage as OverviewUser}
|
||||||
|
|
||||||
<img src={friend.image} alt={friend!.username} class="size-6 rounded-full" />
|
<img src={friend.image} alt={friend!.username} class="size-6 rounded-full" />
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
{friend!.username} [{friend.status == Status.ONLINE
|
{friend!.username}
|
||||||
? 'Online!'
|
|
||||||
: friend.status == Status.DND
|
|
||||||
? 'DND'
|
|
||||||
: friend.status == Status.OFFLINE
|
|
||||||
? 'Offline'
|
|
||||||
: 'Unknown'}]
|
|
||||||
</h1>
|
</h1>
|
||||||
{:else if GroupID.is(currentPageID)}
|
{:else if GroupID.is(currentPageID)}
|
||||||
{@const group = currentPage as OverviewGroup}
|
{@const group = currentPage as OverviewGroup}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue