diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
new file mode 100644
index 0000000..a005691
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-action.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
new file mode 100644
index 0000000..a7b0cf7
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte
@@ -0,0 +1,18 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
new file mode 100644
index 0000000..236bcad
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-content.svelte
@@ -0,0 +1,29 @@
+
+
+
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
new file mode 100644
index 0000000..2ec67dc
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-description.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
new file mode 100644
index 0000000..f78b97a
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
new file mode 100644
index 0000000..1835d91
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-header.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
new file mode 100644
index 0000000..a64ee76
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte
new file mode 100644
index 0000000..f0a19a8
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
new file mode 100644
index 0000000..7ef2b5f
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-title.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte
new file mode 100644
index 0000000..b22d1d5
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/alert-dialog.svelte b/src/lib/components/ui/alert-dialog/alert-dialog.svelte
new file mode 100644
index 0000000..7ea78bb
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/alert-dialog.svelte
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/lib/components/ui/alert-dialog/index.ts b/src/lib/components/ui/alert-dialog/index.ts
new file mode 100644
index 0000000..269538e
--- /dev/null
+++ b/src/lib/components/ui/alert-dialog/index.ts
@@ -0,0 +1,37 @@
+import Root from "./alert-dialog.svelte";
+import Portal from "./alert-dialog-portal.svelte";
+import Trigger from "./alert-dialog-trigger.svelte";
+import Title from "./alert-dialog-title.svelte";
+import Action from "./alert-dialog-action.svelte";
+import Cancel from "./alert-dialog-cancel.svelte";
+import Footer from "./alert-dialog-footer.svelte";
+import Header from "./alert-dialog-header.svelte";
+import Overlay from "./alert-dialog-overlay.svelte";
+import Content from "./alert-dialog-content.svelte";
+import Description from "./alert-dialog-description.svelte";
+
+export {
+ Root,
+ Title,
+ Action,
+ Cancel,
+ Portal,
+ Footer,
+ Header,
+ Trigger,
+ Overlay,
+ Content,
+ Description,
+ //
+ Root as AlertDialog,
+ Title as AlertDialogTitle,
+ Action as AlertDialogAction,
+ Cancel as AlertDialogCancel,
+ Portal as AlertDialogPortal,
+ Footer as AlertDialogFooter,
+ Header as AlertDialogHeader,
+ Trigger as AlertDialogTrigger,
+ Overlay as AlertDialogOverlay,
+ Content as AlertDialogContent,
+ Description as AlertDialogDescription,
+};
diff --git a/src/lib/components/ui/card/card-action.svelte b/src/lib/components/ui/card/card-action.svelte
new file mode 100644
index 0000000..cc36c56
--- /dev/null
+++ b/src/lib/components/ui/card/card-action.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-content.svelte b/src/lib/components/ui/card/card-content.svelte
new file mode 100644
index 0000000..bc90b83
--- /dev/null
+++ b/src/lib/components/ui/card/card-content.svelte
@@ -0,0 +1,15 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-description.svelte b/src/lib/components/ui/card/card-description.svelte
new file mode 100644
index 0000000..9b20ac7
--- /dev/null
+++ b/src/lib/components/ui/card/card-description.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-footer.svelte b/src/lib/components/ui/card/card-footer.svelte
new file mode 100644
index 0000000..2d4d0f2
--- /dev/null
+++ b/src/lib/components/ui/card/card-footer.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-header.svelte b/src/lib/components/ui/card/card-header.svelte
new file mode 100644
index 0000000..2501788
--- /dev/null
+++ b/src/lib/components/ui/card/card-header.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card-title.svelte b/src/lib/components/ui/card/card-title.svelte
new file mode 100644
index 0000000..7447231
--- /dev/null
+++ b/src/lib/components/ui/card/card-title.svelte
@@ -0,0 +1,20 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/card.svelte b/src/lib/components/ui/card/card.svelte
new file mode 100644
index 0000000..99448cc
--- /dev/null
+++ b/src/lib/components/ui/card/card.svelte
@@ -0,0 +1,23 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/ui/card/index.ts b/src/lib/components/ui/card/index.ts
new file mode 100644
index 0000000..4d3fce4
--- /dev/null
+++ b/src/lib/components/ui/card/index.ts
@@ -0,0 +1,25 @@
+import Root from "./card.svelte";
+import Content from "./card-content.svelte";
+import Description from "./card-description.svelte";
+import Footer from "./card-footer.svelte";
+import Header from "./card-header.svelte";
+import Title from "./card-title.svelte";
+import Action from "./card-action.svelte";
+
+export {
+ Root,
+ Content,
+ Description,
+ Footer,
+ Header,
+ Title,
+ Action,
+ //
+ Root as Card,
+ Content as CardContent,
+ Description as CardDescription,
+ Footer as CardFooter,
+ Header as CardHeader,
+ Title as CardTitle,
+ Action as CardAction,
+};
diff --git a/src/lib/components/ui/tabs/index.ts b/src/lib/components/ui/tabs/index.ts
new file mode 100644
index 0000000..12d4327
--- /dev/null
+++ b/src/lib/components/ui/tabs/index.ts
@@ -0,0 +1,16 @@
+import Root from "./tabs.svelte";
+import Content from "./tabs-content.svelte";
+import List from "./tabs-list.svelte";
+import Trigger from "./tabs-trigger.svelte";
+
+export {
+ Root,
+ Content,
+ List,
+ Trigger,
+ //
+ Root as Tabs,
+ Content as TabsContent,
+ List as TabsList,
+ Trigger as TabsTrigger,
+};
diff --git a/src/lib/components/ui/tabs/tabs-content.svelte b/src/lib/components/ui/tabs/tabs-content.svelte
new file mode 100644
index 0000000..340d65c
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-content.svelte
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs-list.svelte b/src/lib/components/ui/tabs/tabs-list.svelte
new file mode 100644
index 0000000..08932b6
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-list.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs-trigger.svelte b/src/lib/components/ui/tabs/tabs-trigger.svelte
new file mode 100644
index 0000000..e623b36
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs-trigger.svelte
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/lib/components/ui/tabs/tabs.svelte b/src/lib/components/ui/tabs/tabs.svelte
new file mode 100644
index 0000000..ef6cada
--- /dev/null
+++ b/src/lib/components/ui/tabs/tabs.svelte
@@ -0,0 +1,19 @@
+
+
+
diff --git a/src/lib/index.ts b/src/lib/index.ts
index a34f746..e90df46 100644
--- a/src/lib/index.ts
+++ b/src/lib/index.ts
@@ -3,10 +3,12 @@ import { definePrefix, type Puuid } from "./puuid"
export const UserID = definePrefix("user");
export const GroupID = definePrefix("group");
export const ServerID = definePrefix("srv");
+export const FriendRequestID = definePrefix("frq");
export type UserId = Puuid<"user">;
export type GroupId = Puuid<"group">;
export type ServerId = Puuid<"srv">;
+export type FriendRequestID = Puuid<"frq">;
export const Status: Record = {
OFFLINE: 1,
diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts
index 51a61f6..f739167 100644
--- a/src/lib/server/auth.ts
+++ b/src/lib/server/auth.ts
@@ -1,5 +1,5 @@
import type { RequestEvent } from '@sveltejs/kit';
-import { eq, inArray } from 'drizzle-orm';
+import { eq, inArray, or } from 'drizzle-orm';
import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding';
import { db } from '$lib/server/db';
@@ -94,8 +94,17 @@ export async function validateSessionToken(token: string) {
.where(inArray(table.group.id, (user.groups as string[])))
: [];
+ const friendRequests = await db
+ .select({
+ id: table.friendRequest.id,
+ fromUser: table.friendRequest.fromUser,
+ toUser: table.friendRequest.toUser,
+ })
+ .from(table.friendRequest)
+ .where(or(eq(table.friendRequest.fromUser, user.id), eq(table.friendRequest.toUser, user.id)))
- return { session, user: {...user, servers, friends, groups: groups.map(z => { return { ...z, members: (z.members as string[]).length}})} };
+
+ return { session, user: {...user, servers, friends, groups: groups.map(z => { return { ...z, members: (z.members as string[]).length}}), friendRequests} };
}
export type SessionValidationResult = Awaited>;
diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts
index e9be647..7361790 100644
--- a/src/lib/server/db/schema.ts
+++ b/src/lib/server/db/schema.ts
@@ -41,6 +41,17 @@ export const channel = sqliteTable("channel", {
messages: text('messages', { mode: "json"}).default([]).notNull(),
})
+export const friendRequest = sqliteTable("friendRequest", {
+ id: text('id').primaryKey(),
+ fromUser: text('from_user').notNull().references(() => user.id),
+ toUser: text('to_user').notNull().references(() => user.id),
+})
+
+export const invite = sqliteTable("invite", {
+ id: text('id').primaryKey(),
+ serverId: text('server_id').notNull().references(() => server.id),
+ code: text('code').notNull(),
+})
export type Session = typeof session.$inferSelect;
export type User = typeof user.$inferSelect;
export type Group = typeof group.$inferSelect;
diff --git a/src/routes/app/+page.server.ts b/src/routes/app/+page.server.ts
index 317af1b..a045606 100644
--- a/src/routes/app/+page.server.ts
+++ b/src/routes/app/+page.server.ts
@@ -3,9 +3,10 @@ import { getRequestEvent } from '$app/server';
import type { Actions, PageServerLoad } from './$types';
import { db } from '$lib/server/db';
import * as table from '$lib/server/db/schema';
-import { ServerID } from '$lib';
+import { FriendRequestID, ServerID } from '$lib';
import { eq } from 'drizzle-orm';
-
+import { and } from 'drizzle-orm';
+import { User } from '$lib/server/db/schema';
export const load: PageServerLoad = async () => {
const user = requireLogin();
return { user };
@@ -16,19 +17,98 @@ export const actions = {
addFriend: async ({ request, locals }) => {
const data = await request.formData();
const username = data.get('username');
+ const userId = data.get('userId');
- if (typeof username !== 'string' || username.length < 3) {
- return fail(400, { error: 'Invalid username' });
+ if (username && !userId) {
+ if (typeof username !== 'string' || username.length < 3) {
+ return fail(400, { error: 'Invalid username' });
+ }
+ }
+
+ let user: User[];
+ if(username) {
+ user = await db.select().from(table.user).where(eq(table.user.username, username.toString())).limit(1);
+ } else if(userId) {
+ user = await db.select().from(table.user).where(eq(table.user.id, userId.toString())).limit(1);
+ } else {
+ return fail(400, { error: 'Missing username or userId' });
+ }
+
+
+ if(user?.length == 0)
+ return fail(400, { error: 'User not found' });
+
+ const friendRequest = await db.select()
+ .from(table.friendRequest)
+ .where(and(eq(table.friendRequest.fromUser, user[0].id), eq(table.friendRequest.toUser, locals.user!.id)))
+ .limit(1);
+
+ // user has already sent a request to us
+ // means we want to accept it
+ //
+ if(friendRequest?.length != 0) {
+ await db.delete(table.friendRequest)
+ .where(and(eq(table.friendRequest.fromUser, user[0].id), eq(table.friendRequest.toUser, locals.user!.id)))
+ .limit(1);
+
+ // add other guy to us
+ await db.transaction(async (tx) => {
+ await tx.update(table.user)
+ .set({ friends: locals.user?.friends.map(z => z.id).concat(user[0].id) })
+ .where(eq(table.user.id, locals.user!.id));
+
+ await tx.update(table.user)
+ .set({ friends: (user[0].friends as string[]).concat(locals.user!.id) })
+ .where(eq(table.user.id, user[0].id));
+ });
+
+ return {success: true}
}
- /*await db.friendRequest.create({
- fromId: locals.user.id,
- toUsername: username
- });*/
+ // a request from us has already been sent to user
+ if(locals.user?.friendRequests.find(z => z.toUser == user[0].id && z.fromUser == locals.user!.id))
+ return fail(400, { error: 'Already sent request' });
+
+ await db.insert(table.friendRequest).values({
+ id: FriendRequestID.newV4(),
+ fromUser: locals.user!.id,
+ toUser: user[0].id,
+ });
return { success: true };
},
+ cancelFriendRequest: async ({ request, locals }) => {
+ const data = await request.formData();
+ const requestId = data.get('requestId');
+ if (typeof requestId !== 'string') {
+ return fail(400, { error: 'Invalid request ID' });
+ }
+
+ // fetch the friend request
+ const friendRequest = await db.select()
+ .from(table.friendRequest)
+ .where(eq(table.friendRequest.id, requestId))
+ .limit(1);
+
+ if (!friendRequest?.length) {
+ return fail(404, { error: 'Friend request not found' });
+ }
+
+ const fr = friendRequest[0];
+
+ // only allow cancelling if it's related to current user
+ if (fr.fromUser !== locals.user!.id && fr.toUser !== locals.user!.id) {
+ return fail(403, { error: 'Not allowed' });
+ }
+
+ // delete the request
+ await db.delete(table.friendRequest)
+ .where(eq(table.friendRequest.id, requestId))
+ .limit(1);
+
+ return { success: true };
+},
createGroup: async ({ request, locals }) => {
const data = await request.formData();
const members = data.getAll('member');
@@ -37,10 +117,7 @@ export const actions = {
return fail(400, { error: 'No members selected' });
}
- await db.group.create({
- ownerId: locals.user.id,
- members: members as string[]
- });
+ console.log(data, members, locals)
return { success: true };
},
@@ -53,7 +130,29 @@ export const actions = {
return fail(400, { error: 'Invalid invite' });
}
- await db.server.joinByInvite(invite, locals.user.id);
+ const inv = await db.select().from(table.invite).where(eq(table.invite.code, invite)).limit(1);
+
+ if(inv?.length == 0)
+ return fail(400, { error: 'Invalid invite' });
+
+ const server = await db.select().from(table.server).where(eq(table.server.id, inv[0].serverId)).limit(1);
+
+ if(server?.length == 0)
+ return fail(400, { error: 'Invalid server' });
+
+ if(locals.user!.servers.some(z => z.id == server[0].id))
+ return fail(400, { error: 'Already in server' });
+
+ await db.transaction(async (tx) => {
+ await tx.update(table.user)
+ .set({servers: locals.user!.servers.map(z => z.id).concat([server[0].id])})
+ .where(eq(table.user.id, locals.user!.id));
+
+ await tx.update(table.server)
+ .set({members: (server[0].members as string[]).concat([locals.user!.id])})
+ .where(eq(table.server.id, server[0].id));
+ })
+
return { success: true };
},
@@ -70,7 +169,7 @@ export const actions = {
.values({id: serverId, name, owner: locals.user!.id, members: [ locals.user!.id ]});
await db.update(table.user)
- .set({servers: (locals.user!.servers as string[]).concat([serverId])})
+ .set({servers: locals.user!.servers.map(z => z.id).concat([serverId])})
.where(eq(table.user.id, locals.user!.id));
redirect(303, `/app`);
diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte
index bf957a8..b32ce5b 100644
--- a/src/routes/app/+page.svelte
+++ b/src/routes/app/+page.svelte
@@ -2,14 +2,16 @@
import { Status, type OverviewData,
GroupID, UserID, ServerID,
type GroupId, type ServerId, type UserId,
- type OverviewUser, type OverviewGroup, type OverviewServer } from "$lib";
-
+ type OverviewUser, type OverviewGroup, type OverviewServer,
+ type UserWithStatus} from "$lib";
import type { PageServerData } from './$types';
import AppSidebar from "$lib/components/app-sidebar.svelte";
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
+ import * as AlertDialog from "$lib/components/ui/alert-dialog/index.js";
import { onMount } from "svelte";
-
- let { data }: { data: PageServerData } = $props();
+ import type { ActionData } from './$types';
+ let errorOpen = $state(true);
+ let { form, data }: { form: ActionData, data: PageServerData } = $props();
let currentPageID: (UserId|GroupId|ServerId)|null = $state(null);
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
@@ -19,7 +21,7 @@
servers: []
});
- console.log(data, overview_data)
+ console.log(form, data, overview_data)
$effect(() => {
if (currentPageID) {
@@ -80,27 +82,44 @@
+{#if form}
+
+
+
+ {form?.error ? "Ran into an error." : "Success!"}
+
+ {form?.error || "Action completed succesfully."}
+
+
+
+ Close
+
+
+
+{/if}
+
+
-
+
{#if currentPageID && currentPage}
{#if ServerID.is(currentPageID)}
- {@const server = (currentPage as Server)}
+ {@const server = (currentPage as OverviewServer)}
{server!.name}
{:else if UserID.is(currentPageID)}
- {@const friend = (currentPage as User)}
+ {@const friend = (currentPage as UserWithStatus)}
-
+
- {friend!.name} [{friend.status == Status.ONLINE ? "Online!" : friend.status == Status.DND ? "DND" : friend.status == Status.OFFLINE ? "Offline" : "Unknown"}]
+ {friend!.username} [{friend.status == Status.ONLINE ? "Online!" : friend.status == Status.DND ? "DND" : friend.status == Status.OFFLINE ? "Offline" : "Unknown"}]
{:else if GroupID.is(currentPageID)}
- {@const group = (currentPage as Group)}
+ {@const group = (currentPage as OverviewGroup)}
{group!.name} ({group.members} member{group.members > 1 ? "s" : ""})
{/if}