diff --git a/drizzle/0001_tiresome_stature.sql b/drizzle/0001_tiresome_stature.sql new file mode 100644 index 0000000..f39db5c --- /dev/null +++ b/drizzle/0001_tiresome_stature.sql @@ -0,0 +1,3 @@ +ALTER TABLE `group` ADD `change_title` integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE `group` ADD `add_members` integer DEFAULT 1 NOT NULL;--> statement-breakpoint +ALTER TABLE `group` ADD `remove_members` integer DEFAULT 0 NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..f81626a --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,556 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "e21369b9-d475-4c0f-8a6d-1c5f92ab8948", + "prevId": "bce65872-fa2f-4adc-b86f-af9880038bc8", + "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": {} + }, + "directMessage": { + "name": "directMessage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "first_member": { + "name": "first_member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "second_member": { + "name": "second_member", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "messages": { + "name": "messages", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + } + }, + "indexes": {}, + "foreignKeys": { + "directMessage_first_member_user_id_fk": { + "name": "directMessage_first_member_user_id_fk", + "tableFrom": "directMessage", + "tableTo": "user", + "columnsFrom": [ + "first_member" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directMessage_second_member_user_id_fk": { + "name": "directMessage_second_member_user_id_fk", + "tableFrom": "directMessage", + "tableTo": "user", + "columnsFrom": [ + "second_member" + ], + "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 + }, + "from_username": { + "name": "from_username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_username": { + "name": "to_username", + "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_from_username_user_id_fk": { + "name": "friendRequest_from_username_user_id_fk", + "tableFrom": "friendRequest", + "tableTo": "user", + "columnsFrom": [ + "from_username" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "friendRequest_to_username_user_id_fk": { + "name": "friendRequest_to_username_user_id_fk", + "tableFrom": "friendRequest", + "tableTo": "user", + "columnsFrom": [ + "to_username" + ], + "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 + }, + "change_title": { + "name": "change_title", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "add_members": { + "name": "add_members", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "remove_members": { + "name": "remove_members", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "members": { + "name": "members", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "messages": { + "name": "messages", + "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": {} + }, + "invite": { + "name": "invite", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invite_server_id_server_id_fk": { + "name": "invite_server_id_server_id_fk", + "tableFrom": "invite", + "tableTo": "server", + "columnsFrom": [ + "server_id" + ], + "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 + }, + "status_overwrite": { + "name": "status_overwrite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 3 + }, + "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 c838996..986751f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1767559403688, "tag": "0000_amusing_shatterstar", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1767860870240, + "tag": "0001_tiresome_stature", + "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 7be235d..b96f15b 100644 --- a/src/lib/components/app-sidebar.svelte +++ b/src/lib/components/app-sidebar.svelte @@ -168,45 +168,10 @@ {#each data.friends as friend (friend.id)} - - - { - e.preventDefault(); - currentPage = friend.id; - }} - user={friend} - > - - - - - - - - - - Remove Friend - - Are you sure you want to remove {friend.username} from your friends? - - - - - - Cancel - - -
- - -
-
-
-
-
+ {/each} diff --git a/src/lib/components/extra/User.svelte b/src/lib/components/extra/User.svelte index 4eea5d2..63ebfb7 100644 --- a/src/lib/components/extra/User.svelte +++ b/src/lib/components/extra/User.svelte @@ -1,34 +1,53 @@ - { - e.preventDefault(); - await navigator.clipboard.writeText(user.id); - }} - href="##" - class="flex items-center gap-2" -> -
+ diff --git a/src/lib/components/member-sidebar.svelte b/src/lib/components/member-sidebar.svelte index 0a0a15a..f7d28e9 100644 --- a/src/lib/components/member-sidebar.svelte +++ b/src/lib/components/member-sidebar.svelte @@ -1,36 +1,128 @@ + +
+ {#if user && currentEntityId} + + + + + Group Settings + Configure your group settings here. + + + + + User Permissions + {#if user.id == currentEntity.ownerId} + Admin Settings + {/if} + + {#if ServerID.is(currentEntityId)} +

not done yet for later

+ {:else if GroupID.is(currentEntityId)} + {#if user.id == currentEntity.ownerId} + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+ {/if} + +
+ {#if (currentEntity as OverviewGroup).permissions.addMembers || user.id == currentEntity.ownerId} +

you have permission to add members

+ {/if} + {#if (currentEntity as OverviewGroup).permissions.changeTitle || user.id == currentEntity.ownerId} +

you have permission to change title

+ {/if} + {#if (currentEntity as OverviewGroup).permissions.removeMembers || user.id == currentEntity.ownerId} +

you have permission to remove members

+ {/if} +
+
+ {/if} +
+
+
+ {/if} +
+
Members @@ -41,7 +133,7 @@ {#snippet child({ props })} - + {/snippet} @@ -49,45 +141,5 @@ - - {#if currentEntityId && ServerID.is(currentEntityId) && user} - - Server Actions - - - - - - {#snippet child({ props })} -
- - -
- {/snippet} -
-
- - {#if user.id === members.find((m) => m.id === currentEntityId)?.ownerId} - - - {#snippet child({ props })} -
- - -
- {/snippet} -
-
- {/if} -
-
-
- {/if}
diff --git a/src/lib/index.ts b/src/lib/index.ts index dc54252..7a96895 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -49,6 +49,11 @@ export type OverviewGroup = { ownerId: string; members: number; image: string; + permissions: { + changeTitle: boolean; + addMembers: boolean; + removeMembers: boolean; + }; }; export interface UserWithStatus extends OverviewUser { diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 0ef8352..b25e23d 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -100,7 +100,10 @@ export async function validateSessionToken(token: string) { id: table.group.id, name: table.group.name, ownerId: table.group.owner, - members: table.group.members + members: table.group.members, + changeTitle: table.group.changeTitle, + addMembers: table.group.addMembers, + removeMembers: table.group.removeMembers }) .from(table.group) .where(inArray(table.group.id, user.groups as string[])) @@ -124,7 +127,15 @@ export async function validateSessionToken(token: string) { servers, friends, groups: groups.map((z) => { - return { ...z, members: (z.members as string[]).length }; + return { + ...z, + members: (z.members as string[]).length, + permissions: { + changeTitle: !!z.changeTitle, + addMembers: !!z.addMembers, + removeMembers: !!z.removeMembers + } + }; }), friendRequests } diff --git a/src/lib/server/db/schema.ts b/src/lib/server/db/schema.ts index ec3b684..4d5a8cd 100644 --- a/src/lib/server/db/schema.ts +++ b/src/lib/server/db/schema.ts @@ -1,3 +1,4 @@ +import { boolean } from 'drizzle-orm/singlestore-core'; import { Status } from '../../index.ts'; import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'; @@ -37,6 +38,9 @@ export const group = sqliteTable('group', { owner: text('owner') .notNull() .references(() => user.id), + changeTitle: integer('change_title').default(1).notNull(), + addMembers: integer('add_members').default(1).notNull(), + removeMembers: integer('remove_members').default(0).notNull(), members: text('members', { mode: 'json' }).default([]).notNull(), messages: text('messages', { mode: 'json' }).default([]).notNull() }); diff --git a/src/routes/api/members/[entityId]/+server.ts b/src/routes/api/members/[entityId]/+server.ts index 640364c..37c7565 100644 --- a/src/routes/api/members/[entityId]/+server.ts +++ b/src/routes/api/members/[entityId]/+server.ts @@ -1,4 +1,4 @@ -import { db } from '$lib/server/db'; +import { db, kvStore } from '$lib/server/db'; import * as table from '$lib/server/db/schema'; import { eq, inArray } from 'drizzle-orm'; import { GroupID, ServerID } from '$lib'; @@ -27,6 +27,9 @@ export const GET: RequestHandler = async ({ params }) => { return json({ members: members.map((member) => ({ id: member.id, + status: kvStore.get('user-' + member.id + '-state'), + //@TODO Implement statusmessage + statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : 'not vibing', username: member.username, image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}` })) @@ -48,6 +51,9 @@ export const GET: RequestHandler = async ({ params }) => { return json({ members: members.map((member) => ({ id: member.id, + status: kvStore.get('user-' + member.id + '-state'), + //@TODO Implement statusmessage + statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : 'not vibing', username: member.username, image: `https://api.dicebear.com/7.x/pixel-art/svg?seed=${member.username}` })) diff --git a/src/routes/api/status/[userId]/+server.ts b/src/routes/api/status/[userId]/+server.ts index e04baf1..b82b69f 100644 --- a/src/routes/api/status/[userId]/+server.ts +++ b/src/routes/api/status/[userId]/+server.ts @@ -1,6 +1,7 @@ import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { kvStore } from '$lib/server/db'; +import { Status } from '$lib'; export const GET: RequestHandler = async ({ params }) => { const { userId } = params; @@ -9,6 +10,11 @@ export const GET: RequestHandler = async ({ params }) => { userId, status: kvStore.get('user-' + userId + '-state'), //@TODO Implement statusmessage - statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : null + statusMessage: + kvStore.get('user-' + userId + '-state') != Status.OFFLINE + ? Math.random() > 0.5 + ? 'vibing 🟢' + : 'not vibing' + : '' }); }; diff --git a/src/routes/api/updates/+server.ts b/src/routes/api/updates/+server.ts index c3608e7..fc608e1 100644 --- a/src/routes/api/updates/+server.ts +++ b/src/routes/api/updates/+server.ts @@ -37,6 +37,8 @@ export async function GET({ locals, request }) { const userId = locals.user.id; //@TODO add more to subscribed eventually, server members, et cetera const subscribed = locals.user.friends.map((f) => f.id); + subscribed.push(userId); // shit such as friend requests + const overwrite = locals.user.statusOverwrite; const sessionId = crypto.randomUUID(); diff --git a/src/routes/app/+page.server.ts b/src/routes/app/+page.server.ts index 2d70873..068cceb 100644 --- a/src/routes/app/+page.server.ts +++ b/src/routes/app/+page.server.ts @@ -7,6 +7,7 @@ import { DirectMessageID, FriendRequestID, GroupID, ServerID } from '$lib'; import { eq } from 'drizzle-orm'; import { and } from 'drizzle-orm'; import { type User } from '$lib/server/db/schema'; +import { _sendToSubscribers } from '../api/updates/+server'; export const load: PageServerLoad = async () => { const user = requireLogin(); return { user }; @@ -92,6 +93,8 @@ export const actions = { .where(eq(table.user.id, user[0].id)); }); + _sendToSubscribers(locals.user!.id, { type: 'friends', status: 'accepted' }); + _sendToSubscribers(user[0].id, { type: 'friends', status: 'accepted' }); return { success: true }; } @@ -111,6 +114,8 @@ export const actions = { fromUsername: locals.user!.username }); + _sendToSubscribers(locals.user!.id, { type: 'friends', status: 'sent-request' }); + _sendToSubscribers(user[0].id, { type: 'friends', status: 'new-request' }); return { success: true }; }, removeFriend: async ({ request, locals }) => { @@ -185,6 +190,9 @@ export const actions = { // delete the request await db.delete(table.friendRequest).where(eq(table.friendRequest.id, requestId)).limit(1); + _sendToSubscribers(fr.fromUser, { type: 'friends', status: 'request-cancelled' }); + _sendToSubscribers(fr.toUser, { type: 'friends', status: 'request-cancelled' }); + return { success: true }; }, createGroup: async ({ request, locals }) => { diff --git a/src/routes/app/+page.svelte b/src/routes/app/+page.svelte index 85a8392..64824a2 100644 --- a/src/routes/app/+page.svelte +++ b/src/routes/app/+page.svelte @@ -33,7 +33,7 @@ let { form, data }: { form: ActionData; data: PageServerData } = $props(); let currentPageID: (UserId | GroupId | ServerId) | null = $state(null); let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state(); - + let ownerId: string | null = $state(null); let isMembersTabOpen = $state(true); let members: UserWithStatus[] = $state([]); @@ -72,6 +72,7 @@ members = data.members; } fetchMembers(); + ownerId = (currentPage as OverviewGroup | OverviewServer).ownerId; } else { isMembersTabOpen = false; } @@ -125,6 +126,7 @@ name: z.name, ownerId: z.ownerId, members: z.members, + permissions: z.permissions, image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + z.name }; }); @@ -160,15 +162,22 @@ const json = JSON.parse(e.data) as | { type: 'connected'; sessionId: string } | { type: 'message'; message: ReturnMessage } - | { type: 'status'; id: string; status: 1 | 2 | 3 }; + | { type: 'status'; id: string; status: 1 | 2 | 3 } + | { type: 'friends'; status: string }; + if (json.type == 'friends') { + alert(json.status); + location.reload(); + } if (json.type == 'connected') { console.log('SSE connected. We are sessionID ' + json.sessionId); sessionId = json.sessionId; } if (json.type == 'status') { + //@TODO update everywhere where user is used const friend = overview_data.friends.find((z) => z.id == json.id); + if (friend) { friend.status = json.status; } @@ -325,6 +334,7 @@ bind:open={isMembersTabOpen} user={data.user} {members} + currentEntity={currentPage} currentEntityId={currentPageID} />