diff --git a/.gitignore b/.gitignore index e0297d6..661d2f8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ vite.config.js.timestamp-* vite.config.ts.timestamp-* database.db +kvStore.db* diff --git a/bun.lock b/bun.lock index 463c702..0fcf3e3 100644 --- a/bun.lock +++ b/bun.lock @@ -7,6 +7,7 @@ "dependencies": { "@libsql/client": "^0.15.15", "@node-rs/argon2": "^2.0.2", + "bun-sqlite-key-value": "^1.13.1", "mode-watcher": "^1.1.0", "postgres": "^3.4.7", }, @@ -396,6 +397,8 @@ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + "bun-sqlite-key-value": ["bun-sqlite-key-value@1.13.1", "", { "peerDependencies": { "typescript": "^5.5.3" } }, "sha512-cb3thB8QXPeXB6B7NhObpADEYvtVNwqg/0ED7PgKt2OxVAxPSejkiTsy1+byQDC0AwLYajw3nhtr/ubKvcLcKw=="], + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], diff --git a/drizzle/0004_fair_stature.sql b/drizzle/0004_fair_stature.sql new file mode 100644 index 0000000..03d602c --- /dev/null +++ b/drizzle/0004_fair_stature.sql @@ -0,0 +1,2 @@ +ALTER TABLE `user` ADD `servers` text DEFAULT '[]' NOT NULL;--> statement-breakpoint +ALTER TABLE `user` ADD `groups` text DEFAULT '[]' NOT NULL; \ No newline at end of file diff --git a/drizzle/0005_mute_mephisto.sql b/drizzle/0005_mute_mephisto.sql new file mode 100644 index 0000000..7669217 --- /dev/null +++ b/drizzle/0005_mute_mephisto.sql @@ -0,0 +1,7 @@ +CREATE TABLE `channel` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `server_id` text NOT NULL, + `messages` text DEFAULT '[]' NOT NULL, + FOREIGN KEY (`server_id`) REFERENCES `server`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 0000000..3ffc7cb --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,254 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "8592fa88-aea8-41f9-a183-8608ec4c4323", + "prevId": "218a80a3-4754-48c7-8173-099613497b99", + "tables": { + "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/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..0e2f7dd --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,307 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "69be2f8f-70ca-4016-b10f-60f64a99af73", + "prevId": "8592fa88-aea8-41f9-a183-8608ec4c4323", + "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": {} + }, + "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 f0a4d5a..edab553 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -29,6 +29,20 @@ "when": 1767530666249, "tag": "0003_wise_lucky_pierre", "breakpoints": true + }, + { + "idx": 4, + "version": "6", + "when": 1767530823274, + "tag": "0004_fair_stature", + "breakpoints": true + }, + { + "idx": 5, + "version": "6", + "when": 1767531680914, + "tag": "0005_mute_mephisto", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 1fb2a26..ab183b0 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "dependencies": { "@libsql/client": "^0.15.15", "@node-rs/argon2": "^2.0.2", + "bun-sqlite-key-value": "^1.13.1", "mode-watcher": "^1.1.0", "postgres": "^3.4.7" } diff --git a/src/lib/components/app-sidebar.svelte b/src/lib/components/app-sidebar.svelte index 2823e63..cb28c76 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -1,5 +1,5 @@ @@ -33,75 +33,126 @@
- - - - - - Add a friend - - Add a friend using their username. - - + + + - - - Cancel - - - + +
+ + Add a friend + + Add a friend using their username. + + + + + + + + Cancel + + + +
+
- - - - - - Create a group - - Add friends into your group! - - - {#each data.friends as friend (friend.id)} + + + - { - e.preventDefault(); - currentPage = friend.id; - }} {friend}> + +
+ + Create a group + + Add friends into your group! + + - {/each} - - Cancel - - - + {#each data.friends as friend (friend.id)} + + {/each} + + + + Cancel + + + +
+
+ - - + - - - - - Join a server - - Enter it's link into the input box here. - - - - - Cancel - - - + +
+ + Join a server + + Enter an invite link. + + + + + + + + Cancel + + + +
+
+ + + + + + + +
+ + Create a server + + Name your new server. + + + + + + + + Cancel + + + +
+
+
+
@@ -128,10 +179,10 @@ - { + { e.preventDefault(); currentPage = friend.id; - }} {friend}> + }} user={friend}> {/each} @@ -194,7 +245,7 @@ e.preventDefault(); currentPage = server.id; }} href="##" class="flex items-center gap-2"> - {server.name} + {server.name} {server.name} diff --git a/src/lib/components/Friend.svelte b/src/lib/components/extra/User.svelte similarity index 53% rename from src/lib/components/Friend.svelte rename to src/lib/components/extra/User.svelte index 3df0aca..de2f5f5 100644 --- a/src/lib/components/Friend.svelte +++ b/src/lib/components/extra/User.svelte @@ -1,23 +1,22 @@
- {friend.name} - {#if friend.status === Status.OFFLINE} + {user.username} + {#if user.status === Status.OFFLINE} - {:else if friend.status === Status.DND} + {:else if user.status === Status.DND} - {:else if friend.status === Status.ONLINE} + {:else if user.status === Status.ONLINE} {/if}
- {friend.name} + {user.username}
diff --git a/src/lib/index.ts b/src/lib/index.ts index 3c779a3..a34f746 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -8,33 +8,39 @@ export type UserId = Puuid<"user">; export type GroupId = Puuid<"group">; export type ServerId = Puuid<"srv">; - export const Status: Record = { OFFLINE: 1, DND: 2, ONLINE: 3 } -export interface User { - id: UserId - name: string, - status: 1|2|3, - image: string -} -export interface Group { - id: GroupId - name: string - members: number -} -export interface Server { - id: ServerId - name: string - image: string -} - - -export interface Data { - friends: User[], - groups: Group[], - servers: Server[], +export type OverviewUser = { + id: string; + username: string; + image: string; }; + +export type OverviewServer = { + id: string; + name: string; + ownerId: string; + image: string; +}; +export type OverviewGroup = { + id: string; + name: string; + ownerId: string; + members: number; + image: string; +}; + +export interface OverviewData { + friends: OverviewUser[], + groups: OverviewGroup[], + servers: OverviewServer[], +}; + +export interface UserWithStatus extends OverviewUser { + status: 1|2|3, + statusMessage: string +} diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 79dac9a..51a61f6 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -1,7 +1,7 @@ import type { RequestEvent } from '@sveltejs/kit'; -import { eq } from 'drizzle-orm'; +import { eq, inArray } from 'drizzle-orm'; import { sha256 } from '@oslojs/crypto/sha2'; -import { encodeBase32LowerCase, encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding'; +import { encodeBase64url, encodeHexLowerCase } from '@oslojs/encoding'; import { db } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; @@ -28,20 +28,25 @@ export async function createSession(token: string, userId: string) { export async function validateSessionToken(token: string) { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); - const [result] = await db + const [row] = await db .select({ - // Adjust user table here to tweak returned data - user: { id: table.user.id, username: table.user.username }, - session: table.session + user: { + id: table.user.id, + username: table.user.username, + friends: table.user.friends, + servers: table.user.servers, + groups: table.user.groups, + }, + session: table.session, }) .from(table.session) .innerJoin(table.user, eq(table.session.userId, table.user.id)) .where(eq(table.session.id, sessionId)); - if (!result) { + if (!row) { return { session: null, user: null }; } - const { session, user } = result; + const { session, user } = row; const sessionExpired = Date.now() >= session.expiresAt.getTime(); if (sessionExpired) { @@ -57,8 +62,40 @@ export async function validateSessionToken(token: string) { .set({ expiresAt: session.expiresAt }) .where(eq(table.session.id, session.id)); } + const friends = (user.friends as string[]).length + ? await db + .select({ + id: table.user.id, + username: table.user.username, + }) + .from(table.user) + .where(inArray(table.user.id, (user.friends as string[]))) + : []; - return { session, user }; + const servers = (user.servers as string[]).length + ? await db + .select({ + id: table.server.id, + name: table.server.name, + ownerId: table.server.owner, + }) + .from(table.server) + .where(inArray(table.server.id, (user.servers as string[]))) + : []; + const groups = (user.groups as string[]).length + ? await db + .select({ + id: table.group.id, + name: table.group.name, + ownerId: table.group.owner, + members: table.group.members + }) + .from(table.group) + .where(inArray(table.group.id, (user.groups as string[]))) + : []; + + + return { session, user: {...user, servers, friends, groups: groups.map(z => { return { ...z, members: (z.members as string[]).length}})} }; } export type SessionValidationResult = Awaited>; @@ -80,14 +117,6 @@ export function deleteSessionTokenCookie(event: RequestEvent) { }); } - -export function generateUserId() { - // ID with 120 bits of entropy, or about the same as UUID v4. - const bytes = crypto.getRandomValues(new Uint8Array(15)); - const id = encodeBase32LowerCase(bytes); - return id; -} - export function validateUsername(username: unknown): username is string { return ( typeof username === 'string' && diff --git a/src/lib/server/db/index.ts b/src/lib/server/db/index.ts index cca5a13..574ee4d 100644 --- a/src/lib/server/db/index.ts +++ b/src/lib/server/db/index.ts @@ -1,5 +1,8 @@ import { drizzle } from 'drizzle-orm/bun-sqlite'; import { Database } from 'bun:sqlite'; +import { BunSqliteKeyValue } from "bun-sqlite-key-value" const sqlite = new Database('database.db'); export const db = drizzle(sqlite); + +export const kvStore = new BunSqliteKeyValue("./kvStore.db") diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index 0c1dba8..e9be647 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -6,6 +6,9 @@ export const user = sqliteTable('user', { email: text('email').notNull().unique(), passwordHash: text('password_hash').notNull(), friends: text('friends', { mode: "json"}).default([]).notNull(), + + servers: text('servers', { mode: "json"}).default([]).notNull(), // string[] of ServerIDs + groups: text('groups', { mode: "json"}).default([]).notNull(), // string[] of GroupIDs }); export const session = sqliteTable('session', { @@ -21,7 +24,7 @@ export const server = sqliteTable("server", { name: text('name').notNull(), owner: text('owner').notNull().references(() => user.id), members: text('members', { mode: "json"}).default([]).notNull(), - channels: text('channels', { mode: "json"}).default([]).notNull(), + channels: text('channels', { mode: "json"}).default([]).notNull(), // string[] of ChannelIDs }) export const group = sqliteTable("group", { @@ -31,6 +34,13 @@ export const group = sqliteTable("group", { members: text('members', { mode: "json"}).default([]).notNull(), }) +export const channel = sqliteTable("channel", { + id: text('id').primaryKey(), + name: text('name').notNull(), + serverId: text('server_id').notNull().references(() => server.id), + messages: text('messages', { mode: "json"}).default([]).notNull(), +}) + export type Session = typeof session.$inferSelect; export type User = typeof user.$inferSelect; export type Group = typeof group.$inferSelect; diff --git a/src/routes/api/messages/[[groupOrServerId]]/[[channelId]]/+server.ts b/src/routes/api/messages/[[groupOrServerId]]/[[channelId]]/+server.ts new file mode 100644 index 0000000..7a78692 --- /dev/null +++ b/src/routes/api/messages/[[groupOrServerId]]/[[channelId]]/+server.ts @@ -0,0 +1,26 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ params }) => { + const { groupOrServerId, channelId } = params; + + const isGroup = !channelId; + + // fake messages + const messages = Array.from({ length: 5 }, (_, i) => ({ + id: crypto.randomUUID(), + authorId: `user_${Math.floor(Math.random() * 10)}`, + content: isGroup + ? `Group message #${i + 1}` + : `Server message #${i + 1}`, + timestamp: Date.now() - Math.floor(Math.random() * 100000), + })); + + return json({ + type: isGroup ? 'group' : 'server', + groupId: isGroup ? groupOrServerId : null, + serverId: isGroup ? null : groupOrServerId, + channelId: channelId ?? null, + messages, + }); +}; diff --git a/src/routes/api/status/[userId]/+server.ts b/src/routes/api/status/[userId]/+server.ts new file mode 100644 index 0000000..488c13d --- /dev/null +++ b/src/routes/api/status/[userId]/+server.ts @@ -0,0 +1,17 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; +import { Status } from '$lib'; + + +export const GET: RequestHandler = async ({ params }) => { + const { userId } = params; + + const status = Object.values(Status)[Math.floor(Math.random() * Object.values(Status).length)]; + + return json({ + userId, + status, + lastActive: Date.now() - Math.floor(Math.random() * 600000), + customStatus: Math.random() > 0.5 ? 'vibing 🟢' : null, + }); +}; diff --git a/src/routes/app/+page.server.ts b/src/routes/app/+page.server.ts index 225a1c5..317af1b 100644 --- a/src/routes/app/+page.server.ts +++ b/src/routes/app/+page.server.ts @@ -1,6 +1,10 @@ -import { redirect } from '@sveltejs/kit'; +import { fail, redirect } from '@sveltejs/kit'; import { getRequestEvent } from '$app/server'; -import type { PageServerLoad } from './$types'; +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 { eq } from 'drizzle-orm'; export const load: PageServerLoad = async () => { const user = requireLogin(); @@ -8,6 +12,71 @@ export const load: PageServerLoad = async () => { }; +export const actions = { + addFriend: async ({ request, locals }) => { + const data = await request.formData(); + const username = data.get('username'); + + if (typeof username !== 'string' || username.length < 3) { + return fail(400, { error: 'Invalid username' }); + } + + /*await db.friendRequest.create({ + fromId: locals.user.id, + toUsername: username + });*/ + + return { success: true }; + }, + + createGroup: async ({ request, locals }) => { + const data = await request.formData(); + const members = data.getAll('member'); + + if (!members.length) { + return fail(400, { error: 'No members selected' }); + } + + await db.group.create({ + ownerId: locals.user.id, + members: members as string[] + }); + + return { success: true }; + }, + + joinServer: async ({ request, locals }) => { + const data = await request.formData(); + const invite = data.get('invite'); + + if (typeof invite !== 'string') { + return fail(400, { error: 'Invalid invite' }); + } + + await db.server.joinByInvite(invite, locals.user.id); + return { success: true }; + }, + + createServer: async ({ request, locals }) => { + const data = await request.formData(); + const name = data.get('name'); + + if (typeof name !== 'string' || name.length < 3) { + return fail(400, { error: 'Server name too short' }); + } + const serverId = ServerID.newV4(); + + await db.insert(table.server) + .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])}) + .where(eq(table.user.id, locals.user!.id)); + + redirect(303, `/app`); + } +} satisfies Actions; + function requireLogin() { const { locals } = getRequestEvent(); diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index ef791c7..bf957a8 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -1,28 +1,25 @@ diff --git a/src/routes/register/+page.server.ts b/src/routes/register/+page.server.ts index 1e3ca56..277da90 100644 --- a/src/routes/register/+page.server.ts +++ b/src/routes/register/+page.server.ts @@ -6,6 +6,7 @@ import * as table from '$lib/server/db/schema'; import type { Actions, PageServerLoad } from './$types'; import { or } from 'drizzle-orm'; import { eq } from 'drizzle-orm'; +import { UserID } from '$lib'; export const load: PageServerLoad = async (event) => { if (event.locals.user) { @@ -33,7 +34,7 @@ export const actions: Actions = { return fail(400, { message: 'Invalid password' }); } - const userId = auth.generateUserId(); + const userId = UserID.newV4(); const passwordHash = await hash(password, { // recommended minimum parameters memoryCost: 19456,