From 126acf52f3b0637d5ddef8786889cdea737ebb42 Mon Sep 17 00:00:00 2001 From: fucksophie Date: Sun, 4 Jan 2026 18:20:04 +0200 Subject: [PATCH] get the whole friends system to work now --- drizzle/0006_gifted_machine_man.sql | 7 + drizzle/meta/0006_snapshot.json | 365 ++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/components/app-sidebar.svelte | 93 ++++- .../alert-dialog/alert-dialog-action.svelte | 18 + .../alert-dialog/alert-dialog-cancel.svelte | 18 + .../alert-dialog/alert-dialog-content.svelte | 29 ++ .../alert-dialog-description.svelte | 17 + .../alert-dialog/alert-dialog-footer.svelte | 20 + .../alert-dialog/alert-dialog-header.svelte | 20 + .../alert-dialog/alert-dialog-overlay.svelte | 20 + .../alert-dialog/alert-dialog-portal.svelte | 7 + .../ui/alert-dialog/alert-dialog-title.svelte | 17 + .../alert-dialog/alert-dialog-trigger.svelte | 7 + .../ui/alert-dialog/alert-dialog.svelte | 7 + src/lib/components/ui/alert-dialog/index.ts | 37 ++ src/lib/components/ui/card/card-action.svelte | 20 + .../components/ui/card/card-content.svelte | 15 + .../ui/card/card-description.svelte | 20 + src/lib/components/ui/card/card-footer.svelte | 20 + src/lib/components/ui/card/card-header.svelte | 23 ++ src/lib/components/ui/card/card-title.svelte | 20 + src/lib/components/ui/card/card.svelte | 23 ++ src/lib/components/ui/card/index.ts | 25 ++ src/lib/components/ui/tabs/index.ts | 16 + .../components/ui/tabs/tabs-content.svelte | 17 + src/lib/components/ui/tabs/tabs-list.svelte | 20 + .../components/ui/tabs/tabs-trigger.svelte | 20 + src/lib/components/ui/tabs/tabs.svelte | 19 + src/lib/index.ts | 2 + src/lib/server/auth.ts | 13 +- src/lib/server/db/schema.ts | 11 + src/routes/app/+page.server.ts | 127 +++++- src/routes/app/+page.svelte | 41 +- 34 files changed, 1101 insertions(+), 40 deletions(-) create mode 100644 drizzle/0006_gifted_machine_man.sql create mode 100644 drizzle/meta/0006_snapshot.json create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-action.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-cancel.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-content.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-description.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-footer.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-header.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-overlay.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-portal.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-title.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog-trigger.svelte create mode 100644 src/lib/components/ui/alert-dialog/alert-dialog.svelte create mode 100644 src/lib/components/ui/alert-dialog/index.ts create mode 100644 src/lib/components/ui/card/card-action.svelte create mode 100644 src/lib/components/ui/card/card-content.svelte create mode 100644 src/lib/components/ui/card/card-description.svelte create mode 100644 src/lib/components/ui/card/card-footer.svelte create mode 100644 src/lib/components/ui/card/card-header.svelte create mode 100644 src/lib/components/ui/card/card-title.svelte create mode 100644 src/lib/components/ui/card/card.svelte create mode 100644 src/lib/components/ui/card/index.ts create mode 100644 src/lib/components/ui/tabs/index.ts create mode 100644 src/lib/components/ui/tabs/tabs-content.svelte create mode 100644 src/lib/components/ui/tabs/tabs-list.svelte create mode 100644 src/lib/components/ui/tabs/tabs-trigger.svelte create mode 100644 src/lib/components/ui/tabs/tabs.svelte diff --git a/drizzle/0006_gifted_machine_man.sql b/drizzle/0006_gifted_machine_man.sql new file mode 100644 index 0000000..6f24705 --- /dev/null +++ b/drizzle/0006_gifted_machine_man.sql @@ -0,0 +1,7 @@ +CREATE TABLE `friendRequest` ( + `id` text PRIMARY KEY NOT NULL, + `from_user` text NOT NULL, + `to_user` text NOT NULL, + FOREIGN KEY (`from_user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`to_user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..7ac207d --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,365 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e809c266-891b-4355-a87c-facd55a5293a", + "prevId": "69be2f8f-70ca-4016-b10f-60f64a99af73", + "tables": { + "channel": { + "name": "channel", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "messages": { + "name": "messages", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_server_id_server_id_fk": { + "name": "channel_server_id_server_id_fk", + "tableFrom": "channel", + "tableTo": "server", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "friendRequest": { + "name": "friendRequest", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "from_user": { + "name": "from_user", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_user": { + "name": "to_user", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "friendRequest_from_user_user_id_fk": { + "name": "friendRequest_from_user_user_id_fk", + "tableFrom": "friendRequest", + "tableTo": "user", + "columnsFrom": [ + "from_user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "friendRequest_to_user_user_id_fk": { + "name": "friendRequest_to_user_user_id_fk", + "tableFrom": "friendRequest", + "tableTo": "user", + "columnsFrom": [ + "to_user" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + } + }, + "indexes": {}, + "foreignKeys": { + "group_owner_user_id_fk": { + "name": "group_owner_user_id_fk", + "tableFrom": "group", + "tableTo": "user", + "columnsFrom": [ + "owner" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "server": { + "name": "server", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "channels": { + "name": "channels", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + } + }, + "indexes": {}, + "foreignKeys": { + "server_owner_user_id_fk": { + "name": "server_owner_user_id_fk", + "tableFrom": "server", + "tableTo": "user", + "columnsFrom": [ + "owner" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "friends": { + "name": "friends", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "servers": { + "name": "servers", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "groups": { + "name": "groups", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + } + }, + "indexes": { + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index edab553..f5ee151 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1767531680914, "tag": "0005_mute_mephisto", "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1767537153441, + "tag": "0006_gifted_machine_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index cb28c76..fc99ced 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -1,8 +1,10 @@ @@ -34,22 +37,24 @@ - - -
- - Add a friend - - Add a friend using their username. - - - + + + Add a friend + + Add a friend using their username or manage pending requests. + + + + + Cancel @@ -57,7 +62,69 @@ -
+ + + + + Outgoing + Incoming + + + + + {#if user.friendRequests.filter(r => r.fromUser === user.id).length === 0} +

No outgoing requests

+ {:else} + {#each user.friendRequests.filter(r => r.fromUser === user.id) as request (request.id)} + + + {request.username} + Request sent + + +
+ + +
+
+
+ {/each} + {/if} +
+ + + + {#if user.friendRequests.filter(r => r.toUser === user.id).length === 0} +

No incoming requests

+ {:else} + {#each user.friendRequests.filter(r => r.toUser === user.id) as request (request.id)} + + + {request.username} + Sent you a friend request + + + +
+ + +
+ +
+ + +
+
+
+ {/each} + {/if} +
+
+
+ +
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}

{server!.name}

{:else if UserID.is(currentPageID)} - {@const friend = (currentPage as User)} + {@const friend = (currentPage as UserWithStatus)} - {friend!.name} + {friend!.username} -

{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}