Implement most of status system (still kinda bootycheeks at checking if

you're connected), fionally fix prettierrc
This commit is contained in:
Soph :3 2026-01-04 20:26:42 +02:00
parent 126acf52f3
commit e64910d895
29 changed files with 2001 additions and 879 deletions

View file

@ -1,5 +1,5 @@
{ {
"useTabs": true, "useTabs": false,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "none",
"printWidth": 100, "printWidth": 100,
@ -12,5 +12,5 @@
} }
} }
], ],
"tailwindStylesheet": "./src/routes/layout.css" "tailwindStylesheet": "./src/app.css"
} }

View file

@ -51,9 +51,9 @@
"packages": { "packages": {
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
@ -381,20 +381,10 @@
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "bits-ui": ["bits-ui@2.15.2", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-S8eDbFkZCN17kZ7J9fD3MRXziV9ozjdFt2D3vTr2bvXCl7BtrIqguYt2U/zrFgLdR2erwybvCKv0JXYn8uKLDQ=="],
"better-sqlite3": ["better-sqlite3@12.5.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-WwCZ/5Diz7rsF29o27o0Gcc1Du+l7Zsv7SYtVPG0X3G/uUI1LqdxrQI7c9Hs2FWpqXXERjW9hp6g3/tH7DlVKg=="],
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
"bits-ui": ["bits-ui@2.14.4", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"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-sqlite-key-value": ["bun-sqlite-key-value@1.13.1", "", { "peerDependencies": { "typescript": "^5.5.3" } }, "sha512-cb3thB8QXPeXB6B7NhObpADEYvtVNwqg/0ED7PgKt2OxVAxPSejkiTsy1+byQDC0AwLYajw3nhtr/ubKvcLcKw=="],
@ -407,8 +397,6 @@
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@ -429,10 +417,6 @@
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
@ -447,8 +431,6 @@
"drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
@ -483,8 +465,6 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@ -497,8 +477,6 @@
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
@ -507,16 +485,12 @@
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="],
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
@ -527,18 +501,12 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
@ -611,14 +579,8 @@
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
"mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="], "mode-watcher": ["mode-watcher@1.1.0", "", { "dependencies": { "runed": "^0.25.0", "svelte-toolbelt": "^0.7.1" }, "peerDependencies": { "svelte": "^5.27.0" } }, "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
@ -629,18 +591,12 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"node-abi": ["node-abi@3.85.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
@ -671,8 +627,6 @@
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="], "prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
@ -683,14 +637,8 @@
"promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="],
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
@ -705,8 +653,6 @@
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
@ -715,10 +661,6 @@
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@ -727,8 +669,6 @@
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
@ -755,20 +695,14 @@
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
"tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
"tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
"ts-api-utils": ["ts-api-utils@2.3.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg=="], "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@ -797,8 +731,6 @@
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
@ -815,9 +747,9 @@
"@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], "@rollup/plugin-commonjs/is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
@ -837,8 +769,6 @@
"mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="], "mode-watcher/svelte-toolbelt": ["svelte-toolbelt@0.7.1", "", { "dependencies": { "clsx": "^2.1.1", "runed": "^0.23.2", "style-to-object": "^1.0.8" }, "peerDependencies": { "svelte": "^5.0.0" } }, "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
"vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], "vite/esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],

View file

@ -0,0 +1,9 @@
CREATE TABLE `invite` (
`id` text PRIMARY KEY NOT NULL,
`server_id` text NOT NULL,
`code` text NOT NULL,
FOREIGN KEY (`server_id`) REFERENCES `server`(`id`) ON UPDATE no action ON DELETE no action
);
--> statement-breakpoint
ALTER TABLE `friendRequest` ADD `from_username` text NOT NULL REFERENCES user(id);--> statement-breakpoint
ALTER TABLE `friendRequest` ADD `to_username` text NOT NULL REFERENCES user(id);

View file

@ -0,0 +1,9 @@
CREATE TABLE `directMessage` (
`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
);
--> statement-breakpoint
ALTER TABLE `user` ADD `status_overwrite` integer DEFAULT 3 NOT NULL;

View file

@ -0,0 +1,450 @@
{
"version": "6",
"dialect": "sqlite",
"id": "c0ac7678-4e2c-433f-ac26-73c7b88cc38b",
"prevId": "e809c266-891b-4355-a87c-facd55a5293a",
"tables": {
"channel": {
"name": "channel",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"server_id": {
"name": "server_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"messages": {
"name": "messages",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'[]'"
}
},
"indexes": {},
"foreignKeys": {
"channel_server_id_server_id_fk": {
"name": "channel_server_id_server_id_fk",
"tableFrom": "channel",
"tableTo": "server",
"columnsFrom": [
"server_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"friendRequest": {
"name": "friendRequest",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"from_user": {
"name": "from_user",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"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
},
"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": {}
},
"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
},
"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": {}
}
}

View file

@ -0,0 +1,511 @@
{
"version": "6",
"dialect": "sqlite",
"id": "bd16f3db-5965-4d36-89b7-d259cbd21149",
"prevId": "c0ac7678-4e2c-433f-ac26-73c7b88cc38b",
"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
},
"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": {
"directMessage_server_id_server_id_fk": {
"name": "directMessage_server_id_server_id_fk",
"tableFrom": "directMessage",
"tableTo": "server",
"columnsFrom": [
"server_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"friendRequest": {
"name": "friendRequest",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"from_user": {
"name": "from_user",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"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
},
"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": {}
},
"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": {}
}
}

View file

@ -50,6 +50,20 @@
"when": 1767537153441, "when": 1767537153441,
"tag": "0006_gifted_machine_man", "tag": "0006_gifted_machine_man",
"breakpoints": true "breakpoints": true
},
{
"idx": 7,
"version": "6",
"when": 1767543898231,
"tag": "0007_outstanding_punisher",
"breakpoints": true
},
{
"idx": 8,
"version": "6",
"when": 1767549353944,
"tag": "0008_mute_lilandra",
"breakpoints": true
} }
] ]
} }

View file

@ -1,5 +1,8 @@
import type { Handle } from '@sveltejs/kit'; import type { Handle } from '@sveltejs/kit';
import * as auth from '$lib/server/auth'; import * as auth from '$lib/server/auth';
import { kvStore } from '$lib/server/db';
import { _sendToSubscribers } from './routes/api/updates/+server';
import { Status } from '$lib';
const handleAuth: Handle = async ({ event, resolve }) => { const handleAuth: Handle = async ({ event, resolve }) => {
const sessionToken = event.cookies.get(auth.sessionCookieName); const sessionToken = event.cookies.get(auth.sessionCookieName);
@ -21,6 +24,57 @@ const handleAuth: Handle = async ({ event, resolve }) => {
event.locals.user = user; event.locals.user = user;
event.locals.session = session; event.locals.session = session;
if (event.locals.user) {
const id = event.locals.user.id;
const overwrite = event.locals.user.statusOverwrite as 1 | 2 | 3 | undefined;
const now = Date.now();
const lastActiveKey = `user-${id}-last-active`;
const stateKey = `user-${id}-state`;
const timerKey = `user-${id}-offline-timer`;
const lastActive = kvStore.get<number>(lastActiveKey) ?? 0;
const state = kvStore.get<1 | 2 | 3>(stateKey) ?? Status.OFFLINE;
// always update activity
kvStore.set(lastActiveKey, now);
if (overwrite !== Status.OFFLINE) {
const shouldSend = state === Status.OFFLINE && now - lastActive > 5000;
if (shouldSend) {
const outgoingStatus = overwrite === Status.DND ? Status.DND : Status.ONLINE;
_sendToSubscribers(id, {
type: 'status',
id,
status: outgoingStatus
});
kvStore.set(stateKey, outgoingStatus);
}
}
const existingTimer = kvStore.get<NodeJS.Timeout>(timerKey);
if (existingTimer) {
clearTimeout(existingTimer);
}
const offlineTimer = setTimeout(() => {
const last = kvStore.get<number>(lastActiveKey) ?? 0;
if (Date.now() - last >= 10_000) {
_sendToSubscribers(id, {
type: 'status',
id,
status: Status.OFFLINE
});
kvStore.set(stateKey, Status.OFFLINE);
kvStore.delete(timerKey);
}
}, 10_000);
kvStore.set(timerKey, offlineTimer + '');
}
return resolve(event); return resolve(event);
}; };

View file

@ -1,22 +1,27 @@
<script lang="ts"> <script lang="ts">
import { type Data, type OverviewUser } from "$lib"; import { type Data, type OverviewUser } 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';
import * as Tabs from "$lib/components/ui/tabs/index.js"; import * as Tabs from '$lib/components/ui/tabs/index.js';
import * as Card from "$lib/components/ui/card/index.js"; import * as Card from '$lib/components/ui/card/index.js';
import MessagesSquare from "@lucide/svelte/icons/messages-square"; import MessagesSquare from '@lucide/svelte/icons/messages-square';
import MinusIcon from "@lucide/svelte/icons/minus"; import MinusIcon from '@lucide/svelte/icons/minus';
import PlusIcon from "@lucide/svelte/icons/plus"; import PlusIcon from '@lucide/svelte/icons/plus';
import UserRoundPlus from '@lucide/svelte/icons/user-round-plus'; import UserRoundPlus from '@lucide/svelte/icons/user-round-plus';
import UsersRound from '@lucide/svelte/icons/users-round'; import UsersRound from '@lucide/svelte/icons/users-round';
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 User from "./extra/User.svelte"; import User from './extra/User.svelte';
import type { SessionValidationResult } from "$lib/server/auth"; import type { SessionValidationResult } from '$lib/server/auth';
let { currentPage = $bindable<string|null>(), data, user, ...restProps }: {currentPage: string|null, data: Data, user: SessionValidationResult['user'] }= $props(); let {
currentPage = $bindable<string | null>(),
data,
user,
...restProps
}: { currentPage: string | null; data: Data; user: SessionValidationResult['user'] } = $props();
</script> </script>
<Sidebar.Root {...restProps}> <Sidebar.Root {...restProps}>
@ -25,7 +30,7 @@
<Sidebar.MenuItem> <Sidebar.MenuItem>
<Sidebar.MenuButton size="lg"> <Sidebar.MenuButton size="lg">
<div <div
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg" class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground"
> >
<MessagesSquare class="size-4" /> <MessagesSquare class="size-4" />
</div> </div>
@ -33,18 +38,18 @@
<span class="font-medium">chat.sad.ovh</span> <span class="font-medium">chat.sad.ovh</span>
</div> </div>
</Sidebar.MenuButton> </Sidebar.MenuButton>
<div class="w-full flex gap-2 justify-center"> <div class="flex w-full justify-center gap-2">
<Dialog.Root> <Dialog.Root>
<Dialog.Trigger> <Dialog.Trigger>
<Button variant={user!.friendRequests.length > 0 ? "destructive" : "outline"} size="icon"> <Button
variant={user!.friendRequests.length > 0 ? 'destructive' : 'outline'}
size="icon"
>
<UserRoundPlus /> <UserRoundPlus />
</Button> </Button>
</Dialog.Trigger> </Dialog.Trigger>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Add a friend</Dialog.Title> <Dialog.Title>Add a friend</Dialog.Title>
<Dialog.Description> <Dialog.Description>
@ -56,9 +61,7 @@
<form method="POST" action="?/addFriend" class="mb-4"> <form method="POST" action="?/addFriend" class="mb-4">
<Input name="username" placeholder="username" required class="mb-2" /> <Input name="username" placeholder="username" required class="mb-2" />
<Dialog.Footer> <Dialog.Footer>
<Dialog.Close class={buttonVariants({ variant: "outline" })}> <Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
Cancel
</Dialog.Close>
<Button type="submit">Send request</Button> <Button type="submit">Send request</Button>
</Dialog.Footer> </Dialog.Footer>
</form> </form>
@ -72,10 +75,10 @@
<!-- Outgoing Requests --> <!-- Outgoing Requests -->
<Tabs.Content value="outgoing"> <Tabs.Content value="outgoing">
{#if user.friendRequests.filter(r => r.fromUser === user.id).length === 0} {#if user.friendRequests.filter((r) => r.fromUser === user.id).length === 0}
<p class="text-sm text-muted-foreground">No outgoing requests</p> <p class="text-sm text-muted-foreground">No outgoing requests</p>
{:else} {:else}
{#each user.friendRequests.filter(r => r.fromUser === user.id) as request (request.id)} {#each user.friendRequests.filter((r) => r.fromUser === user.id) as request (request.id)}
<Card.Root class="mb-2"> <Card.Root class="mb-2">
<Card.Header> <Card.Header>
<Card.Title>{request.username}</Card.Title> <Card.Title>{request.username}</Card.Title>
@ -84,9 +87,7 @@
<Card.Footer> <Card.Footer>
<form method="POST" action="?/cancelFriendRequest"> <form method="POST" action="?/cancelFriendRequest">
<input type="hidden" name="requestId" value={request.id} /> <input type="hidden" name="requestId" value={request.id} />
<Button type="submit" variant="outline" size="sm"> <Button type="submit" variant="outline" size="sm">Cancel</Button>
Cancel
</Button>
</form> </form>
</Card.Footer> </Card.Footer>
</Card.Root> </Card.Root>
@ -96,10 +97,10 @@
<!-- Incoming Requests --> <!-- Incoming Requests -->
<Tabs.Content value="incoming"> <Tabs.Content value="incoming">
{#if user.friendRequests.filter(r => r.toUser === user.id).length === 0} {#if user.friendRequests.filter((r) => r.toUser === user.id).length === 0}
<p class="text-sm text-muted-foreground">No incoming requests</p> <p class="text-sm text-muted-foreground">No incoming requests</p>
{:else} {:else}
{#each user.friendRequests.filter(r => r.toUser === user.id) as request (request.id)} {#each user.friendRequests.filter((r) => r.toUser === user.id) as request (request.id)}
<Card.Root class="mb-2"> <Card.Root class="mb-2">
<Card.Header> <Card.Header>
<Card.Title>{request.username}</Card.Title> <Card.Title>{request.username}</Card.Title>
@ -122,9 +123,7 @@
{/if} {/if}
</Tabs.Content> </Tabs.Content>
</Tabs.Root> </Tabs.Root>
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>
<Dialog.Root> <Dialog.Root>
@ -138,26 +137,18 @@
<form method="POST" action="?/createGroup"> <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!</Dialog.Description>
Add friends into your group!
</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"> <label class="flex items-center gap-2">
<input <input type="checkbox" name="member" value={friend.id} />
type="checkbox"
name="member"
value={friend.id}
/>
<User user={friend} /> <User user={friend} />
</label> </label>
{/each} {/each}
<Dialog.Footer> <Dialog.Footer>
<Dialog.Close class={buttonVariants({ variant: "outline" })}> <Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
Cancel
</Dialog.Close>
<Button type="submit">Create group</Button> <Button type="submit">Create group</Button>
</Dialog.Footer> </Dialog.Footer>
</form> </form>
@ -175,17 +166,13 @@
<form method="POST" action="?/joinServer"> <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 an invite link.</Dialog.Description>
Enter an invite link.
</Dialog.Description>
</Dialog.Header> </Dialog.Header>
<Input name="invite" placeholder="invite link" required /> <Input name="invite" placeholder="invite link" required />
<Dialog.Footer> <Dialog.Footer>
<Dialog.Close class={buttonVariants({ variant: "outline" })}> <Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
Cancel
</Dialog.Close>
<Button type="submit">Join</Button> <Button type="submit">Join</Button>
</Dialog.Footer> </Dialog.Footer>
</form> </form>
@ -203,23 +190,18 @@
<form method="POST" action="?/createServer"> <form method="POST" action="?/createServer">
<Dialog.Header> <Dialog.Header>
<Dialog.Title>Create a server</Dialog.Title> <Dialog.Title>Create a server</Dialog.Title>
<Dialog.Description> <Dialog.Description>Name your new server.</Dialog.Description>
Name your new server.
</Dialog.Description>
</Dialog.Header> </Dialog.Header>
<Input name="name" placeholder="Server name" required /> <Input name="name" placeholder="Server name" required />
<Dialog.Footer> <Dialog.Footer>
<Dialog.Close class={buttonVariants({ variant: "outline" })}> <Dialog.Close class={buttonVariants({ variant: 'outline' })}>Cancel</Dialog.Close>
Cancel
</Dialog.Close>
<Button type="submit">Create</Button> <Button type="submit">Create</Button>
</Dialog.Footer> </Dialog.Footer>
</form> </form>
</Dialog.Content> </Dialog.Content>
</Dialog.Root> </Dialog.Root>
</div> </div>
</Sidebar.MenuItem> </Sidebar.MenuItem>
</Sidebar.Menu> </Sidebar.Menu>
@ -232,12 +214,8 @@
<Collapsible.Trigger> <Collapsible.Trigger>
<Sidebar.MenuButton> <Sidebar.MenuButton>
Friends Friends
<PlusIcon <PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
class="ms-auto group-data-[state=open]/collapsible:hidden" <MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
/>
<MinusIcon
class="ms-auto group-data-[state=closed]/collapsible:hidden"
/>
</Sidebar.MenuButton> </Sidebar.MenuButton>
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content> <Collapsible.Content>
@ -245,11 +223,13 @@
{#each data.friends as friend (friend.id)} {#each data.friends as friend (friend.id)}
<Sidebar.MenuSubItem> <Sidebar.MenuSubItem>
<Sidebar.MenuSubButton> <Sidebar.MenuSubButton>
<User
<User onclick={(e) => { onclick={(e) => {
e.preventDefault(); e.preventDefault();
currentPage = friend.id; currentPage = friend.id;
}} user={friend}></User> }}
user={friend}
></User>
</Sidebar.MenuSubButton> </Sidebar.MenuSubButton>
</Sidebar.MenuSubItem> </Sidebar.MenuSubItem>
{/each} {/each}
@ -263,12 +243,8 @@
<Collapsible.Trigger> <Collapsible.Trigger>
<Sidebar.MenuButton> <Sidebar.MenuButton>
Groups Groups
<PlusIcon <PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
class="ms-auto group-data-[state=open]/collapsible:hidden" <MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
/>
<MinusIcon
class="ms-auto group-data-[state=closed]/collapsible:hidden"
/>
</Sidebar.MenuButton> </Sidebar.MenuButton>
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content> <Collapsible.Content>
@ -276,10 +252,13 @@
{#each data.groups as group (group.id)} {#each data.groups as group (group.id)}
<Sidebar.MenuSubItem> <Sidebar.MenuSubItem>
<Sidebar.MenuSubButton> <Sidebar.MenuSubButton>
<a onclick={(e) => { <a
onclick={(e) => {
e.preventDefault(); e.preventDefault();
currentPage = group.id; currentPage = group.id;
}} href="##"> }}
href="##"
>
{group.name} ({group.members} members) {group.name} ({group.members} members)
</a> </a>
</Sidebar.MenuSubButton> </Sidebar.MenuSubButton>
@ -295,12 +274,8 @@
<Collapsible.Trigger> <Collapsible.Trigger>
<Sidebar.MenuButton> <Sidebar.MenuButton>
Servers Servers
<PlusIcon <PlusIcon class="ms-auto group-data-[state=open]/collapsible:hidden" />
class="ms-auto group-data-[state=open]/collapsible:hidden" <MinusIcon class="ms-auto group-data-[state=closed]/collapsible:hidden" />
/>
<MinusIcon
class="ms-auto group-data-[state=closed]/collapsible:hidden"
/>
</Sidebar.MenuButton> </Sidebar.MenuButton>
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content> <Collapsible.Content>
@ -308,11 +283,19 @@
{#each data.servers as server (server.id)} {#each data.servers as server (server.id)}
<Sidebar.MenuSubItem> <Sidebar.MenuSubItem>
<Sidebar.MenuSubButton> <Sidebar.MenuSubButton>
<a onclick={(e) => { <a
onclick={(e) => {
e.preventDefault(); e.preventDefault();
currentPage = server.id; currentPage = server.id;
}} href="##" class="flex items-center gap-2"> }}
<img src={"https://api.dicebear.com/7.x/pixel-art/svg?seed=" + server.name} alt={server.name} class="size-6 rounded-full" /> href="##"
class="flex items-center gap-2"
>
<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>

View file

@ -0,0 +1,9 @@
import { MediaQuery } from "svelte/reactivity";
const DEFAULT_MOBILE_BREAKPOINT = 768;
export class IsMobile extends MediaQuery {
constructor(breakpoint: number = DEFAULT_MOBILE_BREAKPOINT) {
super(`max-width: ${breakpoint - 1}px`);
}
}

View file

@ -1,20 +1,20 @@
import { definePrefix, type Puuid } from "./puuid" import { definePrefix, type Puuid } from './puuid';
export const UserID = definePrefix("user"); export const UserID = definePrefix('user');
export const GroupID = definePrefix("group"); export const GroupID = definePrefix('group');
export const ServerID = definePrefix("srv"); export const ServerID = definePrefix('srv');
export const FriendRequestID = definePrefix("frq"); export const FriendRequestID = definePrefix('frq');
export type UserId = Puuid<"user">; 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 type FriendRequestID = Puuid<"frq">; export type FriendRequestID = Puuid<'frq'>;
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 type OverviewUser = { export type OverviewUser = {
id: string; id: string;
@ -36,13 +36,13 @@ export type OverviewGroup = {
image: string; image: string;
}; };
export interface OverviewData {
friends: OverviewUser[],
groups: OverviewGroup[],
servers: OverviewServer[],
};
export interface UserWithStatus extends OverviewUser { export interface UserWithStatus extends OverviewUser {
status: 1|2|3, status: 1 | 2 | 3;
statusMessage: string statusMessage: string;
}
export interface OverviewData {
friends: UserWithStatus[];
groups: OverviewGroup[];
servers: OverviewServer[];
} }

View file

@ -1,21 +1,17 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from 'uuid';
import { v7 as uuidv7 } from "uuid"; import { v7 as uuidv7 } from 'uuid';
type Brand<K, T> = K & { __brand: T }; type Brand<K, T> = K & { __brand: T };
export type Puuid<Prefix extends string> = Brand< export type Puuid<Prefix extends string> = Brand<string, { prefix: Prefix }>;
string,
{ prefix: Prefix }
>;
export function definePrefix<const P extends string>(prefix: P) { export function definePrefix<const P extends string>(prefix: P) {
const withPrefix = (uuid: string) => const withPrefix = (uuid: string) => `${prefix}_${uuid}` as Puuid<P>;
`${prefix}_${uuid}` as Puuid<P>;
return { return {
prefix, prefix,
is(value: string): value is Puuid<P> { is(value: string): value is Puuid<P> {
return value.startsWith(prefix + "_"); return value.startsWith(prefix + '_');
}, },
newV7(): Puuid<P> { newV7(): Puuid<P> {
@ -27,16 +23,14 @@ export function definePrefix<const P extends string>(prefix: P) {
}, },
parse(value: string): Puuid<P> { parse(value: string): Puuid<P> {
if (!value.startsWith(prefix + "_")) { if (!value.startsWith(prefix + '_')) {
throw new Error( throw new Error(`Invalid prefix, expected "${prefix}_"`);
`Invalid prefix, expected "${prefix}_"`
);
} }
return value as Puuid<P>; return value as Puuid<P>;
}, },
inner(id: Puuid<P>): string { inner(id: Puuid<P>): string {
return id.slice(prefix.length + 1); return id.slice(prefix.length + 1);
}, }
}; };
} }

View file

@ -36,8 +36,9 @@ export async function validateSessionToken(token: string) {
friends: table.user.friends, friends: table.user.friends,
servers: table.user.servers, servers: table.user.servers,
groups: table.user.groups, groups: table.user.groups,
statusOverwrite: table.user.statusOverwrite
}, },
session: table.session, 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))
@ -66,10 +67,10 @@ export async function validateSessionToken(token: string) {
? await db ? await db
.select({ .select({
id: table.user.id, id: table.user.id,
username: table.user.username, username: table.user.username
}) })
.from(table.user) .from(table.user)
.where(inArray(table.user.id, (user.friends as string[]))) .where(inArray(table.user.id, user.friends as string[]))
: []; : [];
const servers = (user.servers as string[]).length const servers = (user.servers as string[]).length
@ -77,10 +78,10 @@ export async function validateSessionToken(token: string) {
.select({ .select({
id: table.server.id, id: table.server.id,
name: table.server.name, name: table.server.name,
ownerId: table.server.owner, ownerId: table.server.owner
}) })
.from(table.server) .from(table.server)
.where(inArray(table.server.id, (user.servers as string[]))) .where(inArray(table.server.id, user.servers as string[]))
: []; : [];
const groups = (user.groups as string[]).length const groups = (user.groups as string[]).length
? await db ? await db
@ -91,20 +92,30 @@ export async function validateSessionToken(token: string) {
members: table.group.members members: table.group.members
}) })
.from(table.group) .from(table.group)
.where(inArray(table.group.id, (user.groups as string[]))) .where(inArray(table.group.id, user.groups as string[]))
: []; : [];
const friendRequests = await db const friendRequests = await db
.select({ .select({
id: table.friendRequest.id, id: table.friendRequest.id,
fromUser: table.friendRequest.fromUser, fromUser: table.friendRequest.fromUser,
toUser: table.friendRequest.toUser, toUser: table.friendRequest.toUser
}) })
.from(table.friendRequest) .from(table.friendRequest)
.where(or(eq(table.friendRequest.fromUser, user.id), eq(table.friendRequest.toUser, user.id))) .where(or(eq(table.friendRequest.fromUser, user.id), eq(table.friendRequest.toUser, user.id)));
return {
return { session, user: {...user, servers, friends, groups: groups.map(z => { return { ...z, members: (z.members as string[]).length}}), friendRequests} }; session,
user: {
...user,
servers,
friends,
groups: groups.map((z) => {
return { ...z, members: (z.members as string[]).length };
}),
friendRequests
}
};
} }
export type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken>>; export type SessionValidationResult = Awaited<ReturnType<typeof validateSessionToken>>;
@ -139,7 +150,6 @@ export function validatePassword(password: unknown): password is string {
return typeof password === 'string' && password.length >= 6 && password.length <= 255; return typeof password === 'string' && password.length >= 6 && password.length <= 255;
} }
export function validateEmail(email: unknown): email is string { export function validateEmail(email: unknown): email is string {
return ( return (
typeof email === 'string' && typeof email === 'string' &&

View file

@ -1,8 +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" 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") export const kvStore = new BunSqliteKeyValue('./kvStore.db');

View file

@ -1,3 +1,4 @@
import { Status } from '../../index.ts';
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core'; import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
export const user = sqliteTable('user', { export const user = sqliteTable('user', {
@ -5,10 +6,11 @@ export const user = sqliteTable('user', {
username: text('username').notNull().unique(), username: text('username').notNull().unique(),
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(), statusOverwrite: integer('status_overwrite').default(Status.ONLINE).notNull(),
friends: text('friends', { mode: 'json' }).default([]).notNull(),
servers: text('servers', { mode: "json"}).default([]).notNull(), // string[] of ServerIDs servers: text('servers', { mode: 'json' }).default([]).notNull(), // string[] of ServerIDs
groups: text('groups', { mode: "json"}).default([]).notNull(), // string[] of GroupIDs groups: text('groups', { mode: 'json' }).default([]).notNull() // string[] of GroupIDs
}); });
export const session = sqliteTable('session', { export const session = sqliteTable('session', {
@ -19,39 +21,66 @@ export const session = sqliteTable('session', {
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull() expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull()
}); });
export const server = sqliteTable("server", { export const server = sqliteTable('server', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
name: text('name').notNull(), name: text('name').notNull(),
owner: text('owner').notNull().references(() => user.id), owner: text('owner')
members: text('members', { mode: "json"}).default([]).notNull(), .notNull()
channels: text('channels', { mode: "json"}).default([]).notNull(), // string[] of ChannelIDs .references(() => user.id),
}) members: text('members', { mode: 'json' }).default([]).notNull(),
channels: text('channels', { mode: 'json' }).default([]).notNull() // string[] of ChannelIDs
});
export const group = sqliteTable("group", { export const group = sqliteTable('group', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
name: text('name').notNull(), name: text('name').notNull(),
owner: text('owner').notNull().references(() => user.id), owner: text('owner')
members: text('members', { mode: "json"}).default([]).notNull(), .notNull()
}) .references(() => user.id),
members: text('members', { mode: 'json' }).default([]).notNull()
});
export const channel = sqliteTable("channel", { export const channel = sqliteTable('channel', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
name: text('name').notNull(), name: text('name').notNull(),
serverId: text('server_id').notNull().references(() => server.id), serverId: text('server_id')
messages: text('messages', { mode: "json"}).default([]).notNull(), .notNull()
}) .references(() => server.id),
messages: text('messages', { mode: 'json' }).default([]).notNull()
});
export const friendRequest = sqliteTable("friendRequest", { export const directMessage = sqliteTable('directMessage', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
fromUser: text('from_user').notNull().references(() => user.id), name: text('name').notNull(),
toUser: text('to_user').notNull().references(() => user.id), serverId: text('server_id')
}) .notNull()
.references(() => server.id),
messages: text('messages', { mode: 'json' }).default([]).notNull()
});
export const invite = sqliteTable("invite", { export const friendRequest = sqliteTable('friendRequest', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
serverId: text('server_id').notNull().references(() => server.id), fromUser: text('from_user')
code: text('code').notNull(), .notNull()
}) .references(() => user.id),
fromUsername: text('from_username')
.notNull()
.references(() => user.id),
toUsername: text('to_username')
.notNull()
.references(() => user.id),
toUser: text('to_user')
.notNull()
.references(() => user.id)
});
export const invite = sqliteTable('invite', {
id: text('id').primaryKey(),
serverId: text('server_id')
.notNull()
.references(() => server.id),
code: text('code').notNull()
});
export type Session = typeof session.$inferSelect; export type 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;

View file

@ -1,13 +1,13 @@
import { clsx, type ClassValue } from "clsx"; import { clsx, type ClassValue } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T; export type WithoutChild<T> = T extends { child?: any } ? Omit<T, 'child'> : T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T; export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, 'children'> : T;
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>; export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null }; export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import '../app.css'; import '../app.css';
import favicon from '$lib/assets/favicon.svg'; import favicon from '$lib/assets/favicon.svg';
import { ModeWatcher } from "mode-watcher"; import { ModeWatcher } from 'mode-watcher';
let { children } = $props(); let { children } = $props();
</script> </script>

View file

@ -3,7 +3,7 @@ import { fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async (event) => { export const load: PageServerLoad = async (event) => {
if (event.request.url.endsWith("/?logout")) { if (event.request.url.endsWith('/?logout')) {
if (!event.locals.session) { if (!event.locals.session) {
return fail(401); return fail(401);
} }

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import Title from "$lib/components/extra/Title.svelte"; import Title from '$lib/components/extra/Title.svelte';
import Button from "$lib/components/ui/button/button.svelte"; import Button from '$lib/components/ui/button/button.svelte';
import type { PageServerData } from './$types'; import type { PageServerData } from './$types';
let { data }: { data: PageServerData } = $props(); let { data }: { data: PageServerData } = $props();
@ -8,36 +8,46 @@
<div class="p-6 md:w-1/2"> <div class="p-6 md:w-1/2">
<Title class="text-5xl"></Title> <Title class="text-5xl"></Title>
<div class="pb-2">A extremely simple chatting application. Supports mobile devices (as a PWA), and also works on almost every single screen size (responsive design!)</div> <div class="pb-2">
A extremely simple chatting application. Supports mobile devices (as a PWA), and also works on
almost every single screen size (responsive design!)
</div>
<div class="pb-2"> <div class="pb-2">
Featurelist: Featurelist:
<ul class="list-disc pl-6"> <ul class="list-disc pl-6">
<li>Lorem Ipsum is simply dummy text of the printing and typesetting industry. </li> <li>Lorem Ipsum is simply dummy text of the printing and typesetting industry.</li>
<li>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,</li> <li>Lorem Ipsum has been the industry's standard dummy text ever since the 1500s,</li>
<li>when an unknown printer took a galley of type and scrambled it to make a type specimen book.</li> <li>
<li>It has survived not only five centuries, but also the leap into electronic typesetting,</li> when an unknown printer took a galley of type and scrambled it to make a type specimen book.
<li>remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages,</li> </li>
<li>
It has survived not only five centuries, but also the leap into electronic typesetting,
</li>
<li>
remaining essentially unchanged. It was popularised in the 1960s with the release of
Letraset sheets containing Lorem Ipsum passages,
</li>
</ul> </ul>
</div> </div>
<div class="pb-2"> <div class="pb-2">
Screenshots Screenshots
<div class="grid grid-cols-2 grid-rows-2 w-76 gap-2"> <div class="grid w-76 grid-cols-2 grid-rows-2 gap-2">
<img src="https://placehold.co/150x150" alt="temporary placeholder image"/> <img src="https://placehold.co/150x150" alt="temporary placeholder image" />
<img src="https://placehold.co/150x150" alt="temporary placeholder image"/> <img src="https://placehold.co/150x150" alt="temporary placeholder image" />
<img src="https://placehold.co/150x150" alt="temporary placeholder image"/> <img src="https://placehold.co/150x150" alt="temporary placeholder image" />
<img src="https://placehold.co/150x150" alt="temporary placeholder image"/> <img src="https://placehold.co/150x150" alt="temporary placeholder image" />
</div> </div>
</div> </div>
<div class="flex flex-col gap-3 w-[calc(80%)]"> <div class="flex w-[calc(80%)] flex-col gap-3">
{#if data.user?.id} {#if data.user?.id}
<Button href='/app'>Enter</Button> <Button href="/app">Enter</Button>
<Button href='/?logout'>Log out</Button> <Button href="/?logout">Log out</Button>
{:else} {:else}
<Button href='/login'>Log in</Button> <Button href='/register'>Register</Button> <Button href="/login">Log in</Button> <Button href="/register">Register</Button>
{/if} {/if}
</div> </div>
</div> </div>

View file

@ -0,0 +1,3 @@
export async function GET() {
return new Response('OK!');
}

View file

@ -10,10 +10,8 @@ export const GET: RequestHandler = async ({ params }) => {
const messages = Array.from({ length: 5 }, (_, i) => ({ const messages = Array.from({ length: 5 }, (_, i) => ({
id: crypto.randomUUID(), id: crypto.randomUUID(),
authorId: `user_${Math.floor(Math.random() * 10)}`, authorId: `user_${Math.floor(Math.random() * 10)}`,
content: isGroup content: isGroup ? `Group message #${i + 1}` : `Server message #${i + 1}`,
? `Group message #${i + 1}` timestamp: Date.now() - Math.floor(Math.random() * 100000)
: `Server message #${i + 1}`,
timestamp: Date.now() - Math.floor(Math.random() * 100000),
})); }));
return json({ return json({
@ -21,6 +19,6 @@ export const GET: RequestHandler = async ({ params }) => {
groupId: isGroup ? groupOrServerId : null, groupId: isGroup ? groupOrServerId : null,
serverId: isGroup ? null : groupOrServerId, serverId: isGroup ? null : groupOrServerId,
channelId: channelId ?? null, channelId: channelId ?? null,
messages, messages
}); });
}; };

View file

@ -1,17 +1,14 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { Status } from '$lib'; import { kvStore } from '$lib/server/db';
export const GET: RequestHandler = async ({ params }) => { export const GET: RequestHandler = async ({ params }) => {
const { userId } = params; const { userId } = params;
const status = Object.values(Status)[Math.floor(Math.random() * Object.values(Status).length)];
return json({ return json({
userId, userId,
status, status: kvStore.get('user-' + userId + '-state'),
lastActive: Date.now() - Math.floor(Math.random() * 600000), //@TODO Implement statusmessage
customStatus: Math.random() > 0.5 ? 'vibing 🟢' : null, statusMessage: Math.random() > 0.5 ? 'vibing 🟢' : null
}); });
}; };

View file

@ -0,0 +1,48 @@
interface SubscribedTo {
subscribed: string[];
userId: string;
controller: ReadableStreamDefaultController;
}
export const _clients = new Map<string, SubscribedTo>();
export function _sendToSubscribers(userId: string, payload: unknown) {
for (const client of _clients) {
if (client[1].subscribed.includes(userId)) {
try {
client[1].controller.enqueue(`data: ${JSON.stringify(payload)}\n\n`);
} catch {
_clients.delete(client[0]);
}
}
}
}
export async function GET({ locals, request }) {
if (!locals.user) {
return new Response('No authentication', { status: 401 });
}
const subscribed = locals.user.friends.map((z) => z.id);
const reqId = crypto.randomUUID();
const stream = new ReadableStream({
start(controller) {
_clients.set(reqId, { subscribed, userId: locals.user!.id, controller });
console.log(`SSE Client opened. ${_clients.size}`);
controller.enqueue(`data: ${JSON.stringify({ type: 'connected' })}\n\n`);
request.signal.addEventListener('abort', () => {
_clients.delete(reqId);
console.log(`SSE Client aborted. ${_clients.size}`);
});
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
}

View file

@ -6,13 +6,12 @@ import * as table from '$lib/server/db/schema';
import { FriendRequestID, ServerID } from '$lib'; import { FriendRequestID, ServerID } from '$lib';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import { and } from 'drizzle-orm'; import { and } from 'drizzle-orm';
import { User } from '$lib/server/db/schema'; import { type User } from '$lib/server/db/schema';
export const load: PageServerLoad = async () => { export const load: PageServerLoad = async () => {
const user = requireLogin(); const user = requireLogin();
return { user }; return { user };
}; };
export const actions = { export const actions = {
addFriend: async ({ request, locals }) => { addFriend: async ({ request, locals }) => {
const data = await request.formData(); const data = await request.formData();
@ -26,53 +25,81 @@ export const actions = {
} }
let user: User[]; let user: User[];
if(username) { if (username) {
user = await db.select().from(table.user).where(eq(table.user.username, username.toString())).limit(1); user = await db
} else if(userId) { .select()
user = await db.select().from(table.user).where(eq(table.user.id, userId.toString())).limit(1); .from(table.user)
.where(eq(table.user.username, username.toString()))
.limit(1);
} else if (userId) {
user = await db
.select()
.from(table.user)
.where(eq(table.user.id, userId.toString()))
.limit(1);
} else { } else {
return fail(400, { error: 'Missing username or userId' }); return fail(400, { error: 'Missing username or userId' });
} }
if (locals.user?.friends.find((z) => z.id == user[0].id)) {
return fail(400, { error: 'Already friends' });
}
if(user?.length == 0) if (user?.length == 0) return fail(400, { error: 'User not found' });
return fail(400, { error: 'User not found' });
const friendRequest = await db.select() const friendRequest = await db
.select()
.from(table.friendRequest) .from(table.friendRequest)
.where(and(eq(table.friendRequest.fromUser, user[0].id), eq(table.friendRequest.toUser, locals.user!.id))) .where(
and(
eq(table.friendRequest.fromUser, user[0].id),
eq(table.friendRequest.toUser, locals.user!.id)
)
)
.limit(1); .limit(1);
// user has already sent a request to us // user has already sent a request to us
// means we want to accept it // means we want to accept it
// //
if(friendRequest?.length != 0) { if (friendRequest?.length != 0) {
await db.delete(table.friendRequest) await db
.where(and(eq(table.friendRequest.fromUser, user[0].id), eq(table.friendRequest.toUser, locals.user!.id))) .delete(table.friendRequest)
.where(
and(
eq(table.friendRequest.fromUser, user[0].id),
eq(table.friendRequest.toUser, locals.user!.id)
)
)
.limit(1); .limit(1);
// add other guy to us // add other guy to us
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
await tx.update(table.user) await tx
.set({ friends: locals.user?.friends.map(z => z.id).concat(user[0].id) }) .update(table.user)
.set({ friends: locals.user?.friends.map((z) => z.id).concat(user[0].id) })
.where(eq(table.user.id, locals.user!.id)); .where(eq(table.user.id, locals.user!.id));
await tx.update(table.user) await tx
.update(table.user)
.set({ friends: (user[0].friends as string[]).concat(locals.user!.id) }) .set({ friends: (user[0].friends as string[]).concat(locals.user!.id) })
.where(eq(table.user.id, user[0].id)); .where(eq(table.user.id, user[0].id));
}); });
return {success: true} return { success: true };
} }
// a request from us has already been sent to user // a request from us has already been sent to user
if(locals.user?.friendRequests.find(z => z.toUser == user[0].id && z.fromUser == locals.user!.id)) if (
locals.user?.friendRequests.find(
(z) => z.toUser == user[0].id && z.fromUser == locals.user!.id
)
)
return fail(400, { error: 'Already sent request' }); return fail(400, { error: 'Already sent request' });
await db.insert(table.friendRequest).values({ await db.insert(table.friendRequest).values({
id: FriendRequestID.newV4(), id: FriendRequestID.newV4(),
fromUser: locals.user!.id, fromUser: locals.user!.id,
toUser: user[0].id, toUser: user[0].id
}); });
return { success: true }; return { success: true };
@ -86,7 +113,8 @@ export const actions = {
} }
// fetch the friend request // fetch the friend request
const friendRequest = await db.select() const friendRequest = await db
.select()
.from(table.friendRequest) .from(table.friendRequest)
.where(eq(table.friendRequest.id, requestId)) .where(eq(table.friendRequest.id, requestId))
.limit(1); .limit(1);
@ -103,12 +131,10 @@ export const actions = {
} }
// delete the request // delete the request
await db.delete(table.friendRequest) await db.delete(table.friendRequest).where(eq(table.friendRequest.id, requestId)).limit(1);
.where(eq(table.friendRequest.id, requestId))
.limit(1);
return { success: true }; return { success: true };
}, },
createGroup: async ({ request, locals }) => { createGroup: async ({ request, locals }) => {
const data = await request.formData(); const data = await request.formData();
const members = data.getAll('member'); const members = data.getAll('member');
@ -117,8 +143,6 @@ export const actions = {
return fail(400, { error: 'No members selected' }); return fail(400, { error: 'No members selected' });
} }
console.log(data, members, locals)
return { success: true }; return { success: true };
}, },
@ -132,26 +156,30 @@ export const actions = {
const inv = await db.select().from(table.invite).where(eq(table.invite.code, invite)).limit(1); const inv = await db.select().from(table.invite).where(eq(table.invite.code, invite)).limit(1);
if(inv?.length == 0) if (inv?.length == 0) return fail(400, { error: 'Invalid invite' });
return fail(400, { error: 'Invalid invite' });
const server = await db.select().from(table.server).where(eq(table.server.id, inv[0].serverId)).limit(1); const server = await db
.select()
.from(table.server)
.where(eq(table.server.id, inv[0].serverId))
.limit(1);
if(server?.length == 0) if (server?.length == 0) return fail(400, { error: 'Invalid server' });
return fail(400, { error: 'Invalid server' });
if(locals.user!.servers.some(z => z.id == server[0].id)) if (locals.user!.servers.some((z) => z.id == server[0].id))
return fail(400, { error: 'Already in server' }); return fail(400, { error: 'Already in server' });
await db.transaction(async (tx) => { await db.transaction(async (tx) => {
await tx.update(table.user) await tx
.set({servers: locals.user!.servers.map(z => z.id).concat([server[0].id])}) .update(table.user)
.set({ servers: locals.user!.servers.map((z) => z.id).concat([server[0].id]) })
.where(eq(table.user.id, locals.user!.id)); .where(eq(table.user.id, locals.user!.id));
await tx.update(table.server) await tx
.set({members: (server[0].members as string[]).concat([locals.user!.id])}) .update(table.server)
.set({ members: (server[0].members as string[]).concat([locals.user!.id]) })
.where(eq(table.server.id, server[0].id)); .where(eq(table.server.id, server[0].id));
}) });
return { success: true }; return { success: true };
}, },
@ -165,11 +193,13 @@ export const actions = {
} }
const serverId = ServerID.newV4(); const serverId = ServerID.newV4();
await db.insert(table.server) await db
.values({id: serverId, name, owner: locals.user!.id, members: [ locals.user!.id ]}); .insert(table.server)
.values({ id: serverId, name, owner: locals.user!.id, members: [locals.user!.id] });
await db.update(table.user) await db
.set({servers: locals.user!.servers.map(z => z.id).concat([serverId])}) .update(table.user)
.set({ servers: locals.user!.servers.map((z) => z.id).concat([serverId]) })
.where(eq(table.user.id, locals.user!.id)); .where(eq(table.user.id, locals.user!.id));
redirect(303, `/app`); redirect(303, `/app`);

View file

@ -1,18 +1,27 @@
<script lang="ts"> <script lang="ts">
import { Status, type OverviewData, import {
GroupID, UserID, ServerID, Status,
type GroupId, type ServerId, type UserId, type OverviewData,
type OverviewUser, type OverviewGroup, type OverviewServer, GroupID,
type UserWithStatus} from "$lib"; UserID,
ServerID,
type GroupId,
type ServerId,
type UserId,
type OverviewUser,
type OverviewGroup,
type OverviewServer,
type UserWithStatus
} from '$lib';
import type { PageServerData } from './$types'; 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 * as AlertDialog from "$lib/components/ui/alert-dialog/index.js"; import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
import { onMount } from "svelte"; import { onMount } from 'svelte';
import type { ActionData } from './$types'; import type { ActionData } from './$types';
let errorOpen = $state(true); let errorOpen = $state(true);
let { form, data }: { form: ActionData, data: PageServerData } = $props(); let { form, data }: { form: ActionData; data: PageServerData } = $props();
let currentPageID: (UserId|GroupId|ServerId)|null = $state(null); let currentPageID: (UserId | GroupId | ServerId) | null = $state(null);
let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state(); let currentPage: OverviewUser | OverviewGroup | OverviewServer | undefined = $state();
const overview_data: OverviewData = $state({ const overview_data: OverviewData = $state({
@ -21,41 +30,39 @@
servers: [] servers: []
}); });
console.log(form, data, overview_data)
$effect(() => { $effect(() => {
if (currentPageID) { if (currentPageID) {
if(UserID.is(currentPageID)) { if (UserID.is(currentPageID)) {
currentPage = overview_data.friends.find(friend => friend.id === currentPageID); currentPage = overview_data.friends.find((friend) => friend.id === currentPageID);
} else if(GroupID.is(currentPageID)) { } else if (GroupID.is(currentPageID)) {
currentPage = overview_data.groups.find(group => group.id === currentPageID); currentPage = overview_data.groups.find((group) => group.id === currentPageID);
} else if(ServerID.is(currentPageID)) { } else if (ServerID.is(currentPageID)) {
currentPage = overview_data.servers.find(server => server.id === currentPageID); currentPage = overview_data.servers.find((server) => server.id === currentPageID);
} }
} else { } else {
currentPage = undefined; currentPage = undefined;
} }
}); });
onMount(()=>{ onMount(() => {
async function run() { async function run() {
overview_data.servers = data.user.servers.map(z => { overview_data.servers = data.user.servers.map((z) => {
return { return {
id: ServerID.parse(z.id), id: ServerID.parse(z.id),
name: z.name, name: z.name,
ownerId: z.ownerId, ownerId: z.ownerId,
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + z.name, image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + z.name
}; };
}) });
overview_data.groups = data.user.groups.map(z => { overview_data.groups = data.user.groups.map((z) => {
return { return {
id: GroupID.parse(z.id), id: GroupID.parse(z.id),
name: z.name, name: z.name,
ownerId: z.ownerId, ownerId: z.ownerId,
members: z.members, members: z.members,
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + z.name, image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + z.name
}; };
}) });
overview_data.friends = await Promise.all( overview_data.friends = await Promise.all(
data.user.friends.map(async (friend) => { data.user.friends.map(async (friend) => {
const res = await fetch(`/api/status/${friend.id}`); const res = await fetch(`/api/status/${friend.id}`);
@ -70,8 +77,8 @@
id: UserID.parse(friend.id), id: UserID.parse(friend.id),
username: friend.username, username: friend.username,
status: status.status, status: status.status,
customStatus: status.customStatus, statusMessage: status.statusMessage,
image: "https://api.dicebear.com/7.x/pixel-art/svg?seed=" + friend.username image: 'https://api.dicebear.com/7.x/pixel-art/svg?seed=' + friend.username
}; };
}) })
); );
@ -80,15 +87,35 @@
run(); run();
}); });
onMount(() => {
const sse = new EventSource('/api/updates');
sse.addEventListener('message', (e) => {
const json = JSON.parse(e.data) as
| { type: 'connected' }
| { type: 'status'; id: string; status: 1 | 2 | 3 };
if (json.type == 'connected') {
console.log('SSE connected.');
}
if (json.type == 'status') {
const friend = overview_data.friends.find((z) => z.id == json.id);
if (friend) {
friend.status = json.status;
}
}
});
});
</script> </script>
{#if form} {#if form}
<AlertDialog.Root bind:open={errorOpen}> <AlertDialog.Root bind:open={errorOpen}>
<AlertDialog.Content> <AlertDialog.Content>
<AlertDialog.Header> <AlertDialog.Header>
<AlertDialog.Title>{form?.error ? "Ran into an error." : "Success!"}</AlertDialog.Title> <AlertDialog.Title>{form?.error ? 'Ran into an error.' : 'Success!'}</AlertDialog.Title>
<AlertDialog.Description> <AlertDialog.Description>
{form?.error || "Action completed succesfully."} {form?.error || 'Action completed succesfully.'}
</AlertDialog.Description> </AlertDialog.Description>
</AlertDialog.Header> </AlertDialog.Header>
<AlertDialog.Footer> <AlertDialog.Footer>
@ -98,7 +125,6 @@
</AlertDialog.Root> </AlertDialog.Root>
{/if} {/if}
<Sidebar.Provider> <Sidebar.Provider>
<AppSidebar bind:currentPage={currentPageID} user={data.user} data={overview_data} /> <AppSidebar bind:currentPage={currentPageID} user={data.user} data={overview_data} />
@ -107,24 +133,32 @@
<Sidebar.Trigger class="-ms-1" /> <Sidebar.Trigger class="-ms-1" />
{#if currentPageID && currentPage} {#if currentPageID && currentPage}
{#if ServerID.is(currentPageID)} {#if ServerID.is(currentPageID)}
{@const server = (currentPage as OverviewServer)} {@const server = currentPage as OverviewServer}
<img src={server!.image} alt={server!.name} class="size-6 rounded-full" /> <img src={server!.image} alt={server!.name} class="size-6 rounded-full" />
<h1>{server!.name}</h1> <h1>{server!.name}</h1>
{:else if UserID.is(currentPageID)} {:else if UserID.is(currentPageID)}
{@const friend = (currentPage as UserWithStatus)} {@const friend = currentPage as UserWithStatus}
<img src={friend.image} alt={friend!.username} class="size-6 rounded-full" /> <img src={friend.image} alt={friend!.username} class="size-6 rounded-full" />
<h1>{friend!.username} [{friend.status == Status.ONLINE ? "Online!" : friend.status == Status.DND ? "DND" : friend.status == Status.OFFLINE ? "Offline" : "Unknown"}]</h1> <h1>
{friend!.username} [{friend.status == Status.ONLINE
? 'Online!'
: friend.status == Status.DND
? 'DND'
: friend.status == Status.OFFLINE
? 'Offline'
: 'Unknown'}]
</h1>
{:else if GroupID.is(currentPageID)} {:else if GroupID.is(currentPageID)}
{@const group = (currentPage as OverviewGroup)} {@const group = currentPage as OverviewGroup}
<h1>{group!.name} ({group.members} member{group.members > 1 ? "s" : ""})</h1> <h1>{group!.name} ({group.members} member{group.members > 1 ? 's' : ''})</h1>
{/if} {/if}
{/if} {/if}
</header> </header>
<h1> this is like lowkirkounely the content, i should put messages and shi here</h1> <h1>this is like lowkirkounely the content, i should put messages and shi here</h1>
</Sidebar.Inset> </Sidebar.Inset>
</Sidebar.Provider> </Sidebar.Provider>

View file

@ -34,9 +34,12 @@ export const actions: Actions = {
return fail(400, { message: 'Invalid password (min 6, max 255 characters)' }); return fail(400, { message: 'Invalid password (min 6, max 255 characters)' });
} }
const results = await db.select() const results = await db
.select()
.from(table.user) .from(table.user)
.where(username_is_email ? eq(table.user.email, username) : eq(table.user.username, username)); .where(
username_is_email ? eq(table.user.email, username) : eq(table.user.username, username)
);
const existingUser = results.at(0); const existingUser = results.at(0);
if (!existingUser) { if (!existingUser) {

View file

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import Title from "$lib/components/extra/Title.svelte"; import Title from '$lib/components/extra/Title.svelte';
import Button from "$lib/components/ui/button/button.svelte"; import Button from '$lib/components/ui/button/button.svelte';
import Input from "$lib/components/ui/input/input.svelte"; import Input from '$lib/components/ui/input/input.svelte';
import type { ActionData } from './$types'; import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props(); let { form }: { form: ActionData } = $props();
</script> </script>
<div class="flex min-h-screen flex-col items-center justify-center gap-2"> <div class="flex min-h-screen flex-col items-center justify-center gap-2">
<div class='w-1/2 p-2 bg-secondary rounded-md '> <div class="w-1/2 rounded-md bg-secondary p-2">
<div class="text-center pb-2"> <div class="pb-2 text-center">
<Title class="text-2xl"></Title> <Title class="text-2xl"></Title>
</div> </div>
<form method="post" action="?/login"> <form method="post" action="?/login">
@ -20,7 +20,5 @@
</form> </form>
<p style="color: red">{form?.message ?? ''}</p> <p style="color: red">{form?.message ?? ''}</p>
</div> </div>
</div> </div>

View file

@ -26,7 +26,7 @@ export const actions: Actions = {
return fail(400, { message: 'Invalid username' }); return fail(400, { message: 'Invalid username' });
} }
if(!auth.validateEmail(email)) { if (!auth.validateEmail(email)) {
return fail(400, { message: 'Invalid email' }); return fail(400, { message: 'Invalid email' });
} }
@ -42,10 +42,10 @@ export const actions: Actions = {
outputLen: 32, outputLen: 32,
parallelism: 1 parallelism: 1
}); });
const results = await db.select() const results = await db
.select()
.from(table.user) .from(table.user)
.where(or(eq(table.user.email, email), .where(or(eq(table.user.email, email), eq(table.user.username, username)));
eq(table.user.username, username)));
const existingUser = results.at(0); const existingUser = results.at(0);
if (existingUser) { if (existingUser) {
@ -53,8 +53,7 @@ export const actions: Actions = {
} }
try { try {
await db.insert(table.user) await db.insert(table.user).values({ id: userId, email, username, passwordHash });
.values({ id: userId, email, username, passwordHash });
const sessionToken = auth.generateSessionToken(); const sessionToken = auth.generateSessionToken();
const session = await auth.createSession(sessionToken, userId); const session = await auth.createSession(sessionToken, userId);

View file

@ -1,15 +1,15 @@
<script lang="ts"> <script lang="ts">
import Title from "$lib/components/extra/Title.svelte"; import Title from '$lib/components/extra/Title.svelte';
import Button from "$lib/components/ui/button/button.svelte"; import Button from '$lib/components/ui/button/button.svelte';
import Input from "$lib/components/ui/input/input.svelte"; import Input from '$lib/components/ui/input/input.svelte';
import type { ActionData } from './$types'; import type { ActionData } from './$types';
let { form }: { form: ActionData } = $props(); let { form }: { form: ActionData } = $props();
</script> </script>
<div class="flex min-h-screen flex-col items-center justify-center gap-2"> <div class="flex min-h-screen flex-col items-center justify-center gap-2">
<div class='w-1/2 p-2 bg-secondary rounded-md '> <div class="w-1/2 rounded-md bg-secondary p-2">
<div class="text-center pb-2"> <div class="pb-2 text-center">
<Title class="text-2xl"></Title> <Title class="text-2xl"></Title>
</div> </div>
<form method="post" action="?/register"> <form method="post" action="?/register">