actually start implementing the backend slowly
This commit is contained in:
parent
37ae49b66e
commit
342fd30d62
19 changed files with 977 additions and 136 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -23,3 +23,4 @@ vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
database.db
|
database.db
|
||||||
|
kvStore.db*
|
||||||
|
|
|
||||||
3
bun.lock
3
bun.lock
|
|
@ -7,6 +7,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libsql/client": "^0.15.15",
|
"@libsql/client": "^0.15.15",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
|
"bun-sqlite-key-value": "^1.13.1",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
},
|
},
|
||||||
|
|
@ -396,6 +397,8 @@
|
||||||
|
|
||||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
"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=="],
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
|
||||||
2
drizzle/0004_fair_stature.sql
Normal file
2
drizzle/0004_fair_stature.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE `user` ADD `servers` text DEFAULT '[]' NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE `user` ADD `groups` text DEFAULT '[]' NOT NULL;
|
||||||
7
drizzle/0005_mute_mephisto.sql
Normal file
7
drizzle/0005_mute_mephisto.sql
Normal file
|
|
@ -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
|
||||||
|
);
|
||||||
254
drizzle/meta/0004_snapshot.json
Normal file
254
drizzle/meta/0004_snapshot.json
Normal file
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
307
drizzle/meta/0005_snapshot.json
Normal file
307
drizzle/meta/0005_snapshot.json
Normal file
|
|
@ -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": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,20 @@
|
||||||
"when": 1767530666249,
|
"when": 1767530666249,
|
||||||
"tag": "0003_wise_lucky_pierre",
|
"tag": "0003_wise_lucky_pierre",
|
||||||
"breakpoints": true
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libsql/client": "^0.15.15",
|
"@libsql/client": "^0.15.15",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
|
"bun-sqlite-key-value": "^1.13.1",
|
||||||
"mode-watcher": "^1.1.0",
|
"mode-watcher": "^1.1.0",
|
||||||
"postgres": "^3.4.7"
|
"postgres": "^3.4.7"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, type Friend , type Group, type Server } from "$lib";
|
import { type Data } from "$lib";
|
||||||
import * as Collapsible from "$lib/components/ui/collapsible/index.js";
|
import * as Collapsible from "$lib/components/ui/collapsible/index.js";
|
||||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||||
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
import * as Dialog from "$lib/components/ui/dialog/index.js";
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
import CirclePlus from '@lucide/svelte/icons/circle-plus';
|
import CirclePlus from '@lucide/svelte/icons/circle-plus';
|
||||||
import Input from "./ui/input/input.svelte";
|
import Input from "./ui/input/input.svelte";
|
||||||
import Button, { buttonVariants } from "./ui/button/button.svelte";
|
import Button, { buttonVariants } from "./ui/button/button.svelte";
|
||||||
import FriendComponent from "./Friend.svelte";
|
import User from "./extra/User.svelte";
|
||||||
|
|
||||||
let { currentPage = $bindable<string|null>(), data, ...restProps }: {currentPage: string|null, data: { friends: Friend[], groups: Group[], servers: Server[] }}= $props();
|
let { currentPage = $bindable<string|null>(), data, ...restProps }: {currentPage: string|null, data: Data }= $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sidebar.Root {...restProps}>
|
<Sidebar.Root {...restProps}>
|
||||||
|
|
@ -38,7 +38,9 @@
|
||||||
<UserRoundPlus />
|
<UserRoundPlus />
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|
||||||
<Dialog.Content class="sm:max-w-[425px]">
|
<Dialog.Content class="sm:max-w-[425px]">
|
||||||
|
<form method="POST" action="?/addFriend">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Add a friend</Dialog.Title>
|
<Dialog.Title>Add a friend</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
|
|
@ -46,11 +48,15 @@
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
<Input></Input>
|
<Input name="username" placeholder="username" required />
|
||||||
|
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
<Dialog.Close class={buttonVariants({ variant: "outline" })}>Cancel</Dialog.Close>
|
<Dialog.Close class={buttonVariants({ variant: "outline" })}>
|
||||||
<Button type="submit">Send request.</Button>
|
Cancel
|
||||||
|
</Dialog.Close>
|
||||||
|
<Button type="submit">Send request</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|
||||||
|
|
@ -60,48 +66,93 @@
|
||||||
<UsersRound />
|
<UsersRound />
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|
||||||
<Dialog.Content class="sm:max-w-[425px]">
|
<Dialog.Content class="sm:max-w-[425px]">
|
||||||
|
<form method="POST" action="?/createGroup">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Create a group</Dialog.Title>
|
<Dialog.Title>Create a group</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
Add friends into your group!
|
Add friends into your group!
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
|
|
||||||
{#each data.friends as friend (friend.id)}
|
{#each data.friends as friend (friend.id)}
|
||||||
|
<label class="flex items-center gap-2">
|
||||||
<FriendComponent onclick={(e) => {
|
<input
|
||||||
e.preventDefault();
|
type="checkbox"
|
||||||
currentPage = friend.id;
|
name="member"
|
||||||
}} {friend}></FriendComponent>
|
value={friend.id}
|
||||||
|
/>
|
||||||
|
<User user={friend} />
|
||||||
|
</label>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
<Dialog.Close class={buttonVariants({ variant: "outline" })}>Cancel</Dialog.Close>
|
<Dialog.Close class={buttonVariants({ variant: "outline" })}>
|
||||||
|
Cancel
|
||||||
|
</Dialog.Close>
|
||||||
<Button type="submit">Create group</Button>
|
<Button type="submit">Create group</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|
||||||
<Dialog.Root>
|
<Dialog.Root>
|
||||||
<Dialog.Trigger>
|
<Dialog.Trigger>
|
||||||
<Button variant="outline" size="icon">
|
<Button variant="outline" size="icon">
|
||||||
<CirclePlus />
|
<CirclePlus />
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
</Dialog.Trigger>
|
</Dialog.Trigger>
|
||||||
|
|
||||||
<Dialog.Content class="sm:max-w-[425px]">
|
<Dialog.Content class="sm:max-w-[425px]">
|
||||||
|
<form method="POST" action="?/joinServer">
|
||||||
<Dialog.Header>
|
<Dialog.Header>
|
||||||
<Dialog.Title>Join a server</Dialog.Title>
|
<Dialog.Title>Join a server</Dialog.Title>
|
||||||
<Dialog.Description>
|
<Dialog.Description>
|
||||||
Enter it's link into the input box here.
|
Enter an invite link.
|
||||||
</Dialog.Description>
|
</Dialog.Description>
|
||||||
</Dialog.Header>
|
</Dialog.Header>
|
||||||
<Input></Input>
|
|
||||||
|
<Input name="invite" placeholder="invite link" required />
|
||||||
|
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
<Dialog.Close class={buttonVariants({ variant: "outline" })}>Cancel</Dialog.Close>
|
<Dialog.Close class={buttonVariants({ variant: "outline" })}>
|
||||||
<Button type="submit">Create group</Button>
|
Cancel
|
||||||
|
</Dialog.Close>
|
||||||
|
<Button type="submit">Join</Button>
|
||||||
</Dialog.Footer>
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
|
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<PlusIcon />
|
||||||
|
</Button>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content class="sm:max-w-[425px]">
|
||||||
|
<form method="POST" action="?/createServer">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>Create a server</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
Name your new server.
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
<Input name="name" placeholder="Server name" required />
|
||||||
|
|
||||||
|
<Dialog.Footer>
|
||||||
|
<Dialog.Close class={buttonVariants({ variant: "outline" })}>
|
||||||
|
Cancel
|
||||||
|
</Dialog.Close>
|
||||||
|
<Button type="submit">Create</Button>
|
||||||
|
</Dialog.Footer>
|
||||||
|
</form>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Sidebar.MenuItem>
|
</Sidebar.MenuItem>
|
||||||
</Sidebar.Menu>
|
</Sidebar.Menu>
|
||||||
|
|
@ -128,10 +179,10 @@
|
||||||
<Sidebar.MenuSubItem>
|
<Sidebar.MenuSubItem>
|
||||||
<Sidebar.MenuSubButton>
|
<Sidebar.MenuSubButton>
|
||||||
|
|
||||||
<FriendComponent onclick={(e) => {
|
<User onclick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
currentPage = friend.id;
|
currentPage = friend.id;
|
||||||
}} {friend}></FriendComponent>
|
}} user={friend}></User>
|
||||||
</Sidebar.MenuSubButton>
|
</Sidebar.MenuSubButton>
|
||||||
</Sidebar.MenuSubItem>
|
</Sidebar.MenuSubItem>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
@ -194,7 +245,7 @@
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
currentPage = server.id;
|
currentPage = server.id;
|
||||||
}} href="##" class="flex items-center gap-2">
|
}} href="##" class="flex items-center gap-2">
|
||||||
<img src={server.image} alt={server.name} class="size-6 rounded-full" />
|
<img src={"https://api.dicebear.com/7.x/pixel-art/svg?seed=" + server.name} alt={server.name} class="size-6 rounded-full" />
|
||||||
{server.name}
|
{server.name}
|
||||||
</a>
|
</a>
|
||||||
</Sidebar.MenuSubButton>
|
</Sidebar.MenuSubButton>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, type Friend } from '$lib';
|
import { Status, type UserWithStatus } from '$lib';
|
||||||
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onclick,
|
onclick,
|
||||||
friend
|
user
|
||||||
}: { onclick?: (e: MouseEvent) => void, friend: Friend } = $props();
|
}: { onclick?: (e: MouseEvent) => void, user: UserWithStatus } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<a {onclick} href="##" class="flex items-center gap-2">
|
<a {onclick} href="##" class="flex items-center gap-2">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<img src={friend.image} alt={friend.name} class="size-6 rounded-full" />
|
<img src={"https://api.dicebear.com/7.x/pixel-art/svg?seed=" + user.username} alt={user.username} class="size-6 rounded-full" />
|
||||||
{#if friend.status === Status.OFFLINE}
|
{#if user.status === Status.OFFLINE}
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"></span>
|
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-gray-500 ring-1 ring-white"></span>
|
||||||
{:else if friend.status === Status.DND}
|
{:else if user.status === Status.DND}
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"></span>
|
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-red-500 ring-1 ring-white"></span>
|
||||||
{:else if friend.status === Status.ONLINE}
|
{:else if user.status === Status.ONLINE}
|
||||||
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"></span>
|
<span class="absolute bottom-0 end-0 block size-2 rounded-full bg-green-500 ring-1 ring-white"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{friend.name}
|
{user.username}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -8,33 +8,39 @@ export type UserId = Puuid<"user">;
|
||||||
export type GroupId = Puuid<"group">;
|
export type GroupId = Puuid<"group">;
|
||||||
export type ServerId = Puuid<"srv">;
|
export type ServerId = Puuid<"srv">;
|
||||||
|
|
||||||
|
|
||||||
export const Status: Record<string, 1|2|3> = {
|
export const Status: Record<string, 1|2|3> = {
|
||||||
OFFLINE: 1,
|
OFFLINE: 1,
|
||||||
DND: 2,
|
DND: 2,
|
||||||
ONLINE: 3
|
ONLINE: 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export type OverviewUser = {
|
||||||
id: UserId
|
id: string;
|
||||||
name: string,
|
username: string;
|
||||||
status: 1|2|3,
|
image: string;
|
||||||
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 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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { RequestEvent } from '@sveltejs/kit';
|
import type { RequestEvent } from '@sveltejs/kit';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq, inArray } from 'drizzle-orm';
|
||||||
import { sha256 } from '@oslojs/crypto/sha2';
|
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 { db } from '$lib/server/db';
|
||||||
import * as table from '$lib/server/db/schema';
|
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) {
|
export async function validateSessionToken(token: string) {
|
||||||
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token)));
|
||||||
const [result] = await db
|
const [row] = await db
|
||||||
.select({
|
.select({
|
||||||
// Adjust user table here to tweak returned data
|
user: {
|
||||||
user: { id: table.user.id, username: table.user.username },
|
id: table.user.id,
|
||||||
session: table.session
|
username: table.user.username,
|
||||||
|
friends: table.user.friends,
|
||||||
|
servers: table.user.servers,
|
||||||
|
groups: table.user.groups,
|
||||||
|
},
|
||||||
|
session: table.session,
|
||||||
})
|
})
|
||||||
.from(table.session)
|
.from(table.session)
|
||||||
.innerJoin(table.user, eq(table.session.userId, table.user.id))
|
.innerJoin(table.user, eq(table.session.userId, table.user.id))
|
||||||
.where(eq(table.session.id, sessionId));
|
.where(eq(table.session.id, sessionId));
|
||||||
|
|
||||||
if (!result) {
|
if (!row) {
|
||||||
return { session: null, user: null };
|
return { session: null, user: null };
|
||||||
}
|
}
|
||||||
const { session, user } = result;
|
const { session, user } = row;
|
||||||
|
|
||||||
const sessionExpired = Date.now() >= session.expiresAt.getTime();
|
const sessionExpired = Date.now() >= session.expiresAt.getTime();
|
||||||
if (sessionExpired) {
|
if (sessionExpired) {
|
||||||
|
|
@ -57,8 +62,40 @@ export async function validateSessionToken(token: string) {
|
||||||
.set({ expiresAt: session.expiresAt })
|
.set({ expiresAt: session.expiresAt })
|
||||||
.where(eq(table.session.id, session.id));
|
.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<ReturnType<typeof validateSessionToken>>;
|
export type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken>>;
|
||||||
|
|
@ -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 {
|
export function validateUsername(username: unknown): username is string {
|
||||||
return (
|
return (
|
||||||
typeof username === 'string' &&
|
typeof username === 'string' &&
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
||||||
import { Database } from 'bun:sqlite';
|
import { Database } from 'bun:sqlite';
|
||||||
|
import { BunSqliteKeyValue } from "bun-sqlite-key-value"
|
||||||
|
|
||||||
const sqlite = new Database('database.db');
|
const sqlite = new Database('database.db');
|
||||||
export const db = drizzle(sqlite);
|
export const db = drizzle(sqlite);
|
||||||
|
|
||||||
|
export const kvStore = new BunSqliteKeyValue("./kvStore.db")
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ export const user = sqliteTable('user', {
|
||||||
email: text('email').notNull().unique(),
|
email: text('email').notNull().unique(),
|
||||||
passwordHash: text('password_hash').notNull(),
|
passwordHash: text('password_hash').notNull(),
|
||||||
friends: text('friends', { mode: "json"}).default([]).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', {
|
export const session = sqliteTable('session', {
|
||||||
|
|
@ -21,7 +24,7 @@ export const server = sqliteTable("server", {
|
||||||
name: text('name').notNull(),
|
name: text('name').notNull(),
|
||||||
owner: text('owner').notNull().references(() => user.id),
|
owner: text('owner').notNull().references(() => user.id),
|
||||||
members: text('members', { mode: "json"}).default([]).notNull(),
|
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", {
|
export const group = sqliteTable("group", {
|
||||||
|
|
@ -31,6 +34,13 @@ export const group = sqliteTable("group", {
|
||||||
members: text('members', { mode: "json"}).default([]).notNull(),
|
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 Session = typeof session.$inferSelect;
|
||||||
export type User = typeof user.$inferSelect;
|
export type User = typeof user.$inferSelect;
|
||||||
export type Group = typeof group.$inferSelect;
|
export type Group = typeof group.$inferSelect;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
17
src/routes/api/status/[userId]/+server.ts
Normal file
17
src/routes/api/status/[userId]/+server.ts
Normal file
|
|
@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import { getRequestEvent } from '$app/server';
|
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 () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const user = requireLogin();
|
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() {
|
function requireLogin() {
|
||||||
const { locals } = getRequestEvent();
|
const { locals } = getRequestEvent();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,25 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Status, type Server, type Group, type User, GroupID, UserID, ServerID, type GroupId, type ServerId, type UserId, type Data } from "$lib";
|
import { Status, type OverviewData,
|
||||||
|
GroupID, UserID, ServerID,
|
||||||
|
type GroupId, type ServerId, type UserId,
|
||||||
|
type OverviewUser, type OverviewGroup, type OverviewServer } from "$lib";
|
||||||
|
|
||||||
|
import type { PageServerData } from './$types';
|
||||||
import AppSidebar from "$lib/components/app-sidebar.svelte";
|
import AppSidebar from "$lib/components/app-sidebar.svelte";
|
||||||
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
import * as Sidebar from "$lib/components/ui/sidebar/index.js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let { data }: { data: PageServerData } = $props();
|
||||||
let currentPageID: (UserId|GroupId|ServerId)|null = $state(null);
|
let currentPageID: (UserId|GroupId|ServerId)|null = $state(null);
|
||||||
let currentPage: User | Group | Server | undefined = $state();
|
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
|
||||||
|
|
||||||
const overview_data: Data = {
|
const overview_data: OverviewData = $state({
|
||||||
friends: [
|
friends: [],
|
||||||
{ id: UserID.newV7(), name: "Alice", status: Status.OFFLINE, image: "https://placehold.co/40x40" },
|
groups: [],
|
||||||
{ id: UserID.newV7(), name: "Bob", status: Status.DND, image: "https://placehold.co/40x40" },
|
servers: []
|
||||||
{ id: UserID.newV7(), name: "Charlie", status: Status.ONLINE, image: "https://placehold.co/40x40" },
|
});
|
||||||
],
|
|
||||||
groups: [
|
|
||||||
{ id: GroupID.newV7(), name: "Work Team", members: 5 },
|
|
||||||
{ id: GroupID.newV7(), name: "Gaming Group", members: 8 },
|
|
||||||
],
|
|
||||||
servers: [
|
|
||||||
{ id: ServerID.newV7(), name: "Main Server", image: "https://placehold.co/40x40" },
|
|
||||||
{ id: ServerID.newV7(), name: "Backup Server", image: "https://placehold.co/40x40" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(overview_data)
|
console.log(data, overview_data)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (currentPageID) {
|
if (currentPageID) {
|
||||||
|
|
@ -37,6 +34,50 @@
|
||||||
currentPage = undefined;
|
currentPage = undefined;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMount(()=>{
|
||||||
|
async function run() {
|
||||||
|
overview_data.servers = data.user.servers.map(z => {
|
||||||
|
return {
|
||||||
|
id: ServerID.parse(z.id),
|
||||||
|
name: z.name,
|
||||||
|
ownerId: z.ownerId,
|
||||||
|
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + z.name,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
overview_data.groups = data.user.groups.map(z => {
|
||||||
|
return {
|
||||||
|
id: GroupID.parse(z.id),
|
||||||
|
name: z.name,
|
||||||
|
ownerId: z.ownerId,
|
||||||
|
members: z.members,
|
||||||
|
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + z.name,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
overview_data.friends = await Promise.all(
|
||||||
|
data.user.friends.map(async (friend) => {
|
||||||
|
const res = await fetch(`/api/status/${friend.id}`);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`Failed to fetch status for ${friend.id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const status = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: UserID.parse(friend.id),
|
||||||
|
username: friend.username,
|
||||||
|
status: status.status,
|
||||||
|
customStatus: status.customStatus,
|
||||||
|
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + friend.username
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sidebar.Provider>
|
<Sidebar.Provider>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import * as table from '$lib/server/db/schema';
|
||||||
import type { Actions, PageServerLoad } from './$types';
|
import type { Actions, PageServerLoad } from './$types';
|
||||||
import { or } from 'drizzle-orm';
|
import { or } from 'drizzle-orm';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { UserID } from '$lib';
|
||||||
|
|
||||||
export const load: PageServerLoad = async (event) => {
|
export const load: PageServerLoad = async (event) => {
|
||||||
if (event.locals.user) {
|
if (event.locals.user) {
|
||||||
|
|
@ -33,7 +34,7 @@ export const actions: Actions = {
|
||||||
return fail(400, { message: 'Invalid password' });
|
return fail(400, { message: 'Invalid password' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = auth.generateUserId();
|
const userId = UserID.newV4();
|
||||||
const passwordHash = await hash(password, {
|
const passwordHash = await hash(password, {
|
||||||
// recommended minimum parameters
|
// recommended minimum parameters
|
||||||
memoryCost: 19456,
|
memoryCost: 19456,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue