From e49d7273b475c44f4a79401a09052f9d081debb9 Mon Sep 17 00:00:00 2001 From: ewsgit Date: Sun, 21 Jun 2026 17:08:36 +0100 Subject: [PATCH] reorganize to fix deno workspace dep conflicts & get apiSystem working, begin cookie migration to use @std/http/cookie as another step away from Bun libs --- .run/index.ts.run.xml | 4 +- .vscode/launch.json | 2 +- README.md | 2 +- {instance => backend}/.gitignore | 0 {instance => backend}/deno.json | 3 +- {instance => backend}/meta/backend.service | 2 +- {instance => backend}/meta/backend.sh | 2 +- {instance => backend}/package.json | 1 - .../src/assets/fs_template_files/markdown.md | 0 .../assets/fs_template_files/plaintext.txt | 0 .../src/assets/placeholder/avatar.png | Bin .../src/assets/placeholder/banner.png | Bin .../src/assets/placeholder/file.png | Bin .../assets/wallpapers/generic_background.png | Bin .../wallpapers/pexels-argoallvee-27976588.jpg | Bin .../pexels-eberhardgross-1624496.jpg | Bin .../wallpapers/pexels-jackerkun-10428721.jpg | Bin .../wallpapers/pexels-jackerkun-10428723.jpg | Bin .../wallpapers/pexels-jackerkun-12126032.jpg | Bin .../wallpapers/pexels-steve-29708303.jpg | Bin .../pexels-tina-p-891488179-33017133.jpg | Bin .../src/assets/wallpapers/stars_wallpaper.png | Bin {instance => backend}/src/index.ts | 19 +- {instance => backend}/src/log.ts | 0 {instance => backend}/src/system.ts | 3 +- {instance => backend}/src/systems/api.ts | 537 +++--- .../src/systems/applications.ts | 0 .../src/systems/applications/application.ts | 0 .../src/systems/applications/serviceStatus.ts | 0 .../src/systems/authorization.ts | 0 .../src/systems/commandBackup/application.ts | 0 .../src/systems/commandBackup/feature.ts | 0 .../commandBackup/install_application.ts | 0 .../src/systems/commandBackup/notify.ts | 0 .../src/systems/commandBackup/op.ts | 0 .../src/systems/commandBackup/password.ts | 0 .../src/systems/commandBackup/restart.ts | 0 .../src/systems/commandBackup/stop.ts | 0 .../src/systems/commandBackup/useradd.ts | 0 .../src/systems/commandBackup/userdel.ts | 0 .../src/systems/commandBackup/users.ts | 0 .../src/systems/configuration.ts | 51 +- .../src/systems/consoleCommands.ts | 0 .../systems/consoleCommands/application.ts | 0 .../src/systems/consoleCommands/exit.ts | 0 {instance => backend}/src/systems/database.ts | 0 {instance => backend}/src/systems/email.ts | 0 {instance => backend}/src/systems/events.ts | 0 .../src/systems/filesystem.ts | 482 +++--- {instance => backend}/src/systems/image.ts | 0 .../src/systems/notifications.ts | 0 .../src/systems/reverseProxy.ts | 0 {instance => backend}/src/systems/settings.ts | 0 .../applicationSetting/applicationSetting.ts | 0 .../applicationSetting/booleanSetting.ts | 0 .../applicationSetting/numberSetting.ts | 0 .../applicationSetting/stringListSetting.ts | 0 .../applicationSetting/stringSetting.ts | 0 {instance => backend}/src/systems/terminal.ts | 0 {instance => backend}/src/systems/trpc.ts | 16 +- .../src/systems/trpcRouter.ts | 1476 ++++++++--------- {instance => backend}/src/systems/users.ts | 0 .../src/systems/webFrontend.ts | 53 +- deno.json | 14 +- deno.lock | 119 +- package.json | 10 +- {instance/src/web => web}/.gitignore | 0 {instance/src/web => web}/deno.json | 0 {instance/src/web => web}/index.html | 0 {instance/src/web => web}/package.json | 0 .../src/web => web}/public/app.webmanifest | 0 .../public/assets/generic_background.png | Bin .../public/assets/generic_background.svg | 0 .../onlineworkspace/online_workspace_logo.svg | 0 .../online_workspace_wordmark.svg | 0 .../public/assets/placeholder/avatar.png | Bin .../assets/placeholder/placeholder_image.png | Bin .../assets/placeholder/placeholder_image.svg | 0 .../src/web => web}/public/fonts/roboto.css | 0 .../web => web}/public/fonts/roboto_flex.css | 0 .../public/fonts/roboto_flex_latin.woff2 | Bin .../public/fonts/roboto_flex_latin_ext.woff2 | Bin .../public/fonts/roboto_italic_latin.woff2 | Bin .../fonts/roboto_italic_latin_ext.woff2 | Bin .../public/fonts/roboto_italic_math.woff2 | Bin .../public/fonts/roboto_italic_symbols.woff2 | Bin .../public/fonts/roboto_latin.woff2 | Bin .../public/fonts/roboto_latin_ext.woff2 | Bin .../public/fonts/roboto_math.woff2 | Bin .../public/fonts/roboto_symbols.woff2 | Bin {instance/src/web => web}/src/App.module.scss | 0 {instance/src/web => web}/src/App.tsx | 0 .../web => web}/src/MissingApp.module.scss | 0 {instance/src/web => web}/src/MissingApp.tsx | 0 {instance/src/web => web}/src/index.scss | 0 {instance/src/web => web}/src/index.tsx | 0 {instance/src/web => web}/src/lib/backend.ts | 0 {instance/src/web => web}/src/lib/trpc.ts | 0 .../src/pages/app/AuthCheck.module.scss | 0 .../web => web}/src/pages/app/AuthCheck.tsx | 0 .../src/web => web}/src/pages/app/Index.tsx | 0 .../src/pages/app/Layout.module.scss | 0 .../src/web => web}/src/pages/app/Layout.tsx | 0 .../src/pages/app/Navigation.module.scss | 0 .../web => web}/src/pages/app/Navigation.tsx | 0 .../NavigationRailApplications.module.scss | 0 .../NavigationRailApplications.tsx | 0 .../NavigationRailAvatar.module.scss | 0 .../NavigationRailAvatar.tsx | 0 .../NavigationRailClock.module.scss | 0 .../NavigationRailClock.tsx | 0 .../NavigationRailNotifications.module.scss | 0 .../NavigationRailNotifications.tsx | 0 .../notification/Notification.module.scss | 0 .../notification/Notification.tsx | 0 .../web => web}/src/pages/notFound/Index.tsx | 0 .../src/pages/userSelect/Layout.module.scss | 0 .../src/pages/userSelect/Layout.tsx | 0 .../forgotPassword/ForgotPassword.module.scss | 0 .../forgotPassword/ForgotPassword.tsx | 0 .../pages/userSelect/login/Login.module.scss | 0 .../src/pages/userSelect/login/Login.tsx | 0 .../userSelect/signup/Signup.module.scss | 0 .../src/pages/userSelect/signup/Signup.tsx | 0 .../signup/stages/Email/Email.module.scss | 0 .../userSelect/signup/stages/Email/Email.tsx | 0 .../userSelect/signup/stages/Guide/Guide.tsx | 0 .../signup/stages/GuidePrompt/GuidePrompt.tsx | 0 .../stages/Password/Password.module.scss | 0 .../signup/stages/Password/Password.tsx | 0 .../components/Requirement/Requirement.tsx | 0 .../signup/stages/Profile/Profile.module.scss | 0 .../signup/stages/Profile/Profile.tsx | 0 .../stages/TermsOfUse/TermsOfUse.module.scss | 0 .../signup/stages/TermsOfUse/TermsOfUse.tsx | 0 .../TwoFactorAuthentication.module.scss | 0 .../TwoFactorAuthentication.tsx | 0 .../stages/Username/Username.module.scss | 0 .../signup/stages/Username/Username.tsx | 0 .../VerifyEmail/VerifyEmail.module.scss | 0 .../signup/stages/VerifyEmail/VerifyEmail.tsx | 0 {instance/src/web => web}/src/vite-env.d.ts | 0 {instance/src/web => web}/tsconfig.app.json | 63 +- {instance/src/web => web}/tsconfig.json | 0 {instance/src/web => web}/tsconfig.node.json | 0 {instance/src/web => web}/vite.config.ts | 67 +- 146 files changed, 1486 insertions(+), 1440 deletions(-) rename {instance => backend}/.gitignore (100%) rename {instance => backend}/deno.json (67%) rename {instance => backend}/meta/backend.service (85%) rename {instance => backend}/meta/backend.sh (80%) rename {instance => backend}/package.json (97%) rename {instance => backend}/src/assets/fs_template_files/markdown.md (100%) rename {instance => backend}/src/assets/fs_template_files/plaintext.txt (100%) rename {instance => backend}/src/assets/placeholder/avatar.png (100%) rename {instance => backend}/src/assets/placeholder/banner.png (100%) rename {instance => backend}/src/assets/placeholder/file.png (100%) rename {instance => backend}/src/assets/wallpapers/generic_background.png (100%) rename {instance => backend}/src/assets/wallpapers/pexels-argoallvee-27976588.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-eberhardgross-1624496.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-jackerkun-10428721.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-jackerkun-10428723.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-jackerkun-12126032.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-steve-29708303.jpg (100%) rename {instance => backend}/src/assets/wallpapers/pexels-tina-p-891488179-33017133.jpg (100%) rename {instance => backend}/src/assets/wallpapers/stars_wallpaper.png (100%) rename {instance => backend}/src/index.ts (92%) rename {instance => backend}/src/log.ts (100%) rename {instance => backend}/src/system.ts (97%) rename {instance => backend}/src/systems/api.ts (86%) rename {instance => backend}/src/systems/applications.ts (100%) rename {instance => backend}/src/systems/applications/application.ts (100%) rename {instance => backend}/src/systems/applications/serviceStatus.ts (100%) rename {instance => backend}/src/systems/authorization.ts (100%) rename {instance => backend}/src/systems/commandBackup/application.ts (100%) rename {instance => backend}/src/systems/commandBackup/feature.ts (100%) rename {instance => backend}/src/systems/commandBackup/install_application.ts (100%) rename {instance => backend}/src/systems/commandBackup/notify.ts (100%) rename {instance => backend}/src/systems/commandBackup/op.ts (100%) rename {instance => backend}/src/systems/commandBackup/password.ts (100%) rename {instance => backend}/src/systems/commandBackup/restart.ts (100%) rename {instance => backend}/src/systems/commandBackup/stop.ts (100%) rename {instance => backend}/src/systems/commandBackup/useradd.ts (100%) rename {instance => backend}/src/systems/commandBackup/userdel.ts (100%) rename {instance => backend}/src/systems/commandBackup/users.ts (100%) rename {instance => backend}/src/systems/configuration.ts (89%) rename {instance => backend}/src/systems/consoleCommands.ts (100%) rename {instance => backend}/src/systems/consoleCommands/application.ts (100%) rename {instance => backend}/src/systems/consoleCommands/exit.ts (100%) rename {instance => backend}/src/systems/database.ts (100%) rename {instance => backend}/src/systems/email.ts (100%) rename {instance => backend}/src/systems/events.ts (100%) rename {instance => backend}/src/systems/filesystem.ts (96%) rename {instance => backend}/src/systems/image.ts (100%) rename {instance => backend}/src/systems/notifications.ts (100%) rename {instance => backend}/src/systems/reverseProxy.ts (100%) rename {instance => backend}/src/systems/settings.ts (100%) rename {instance => backend}/src/systems/settings/applicationSetting/applicationSetting.ts (100%) rename {instance => backend}/src/systems/settings/applicationSetting/booleanSetting.ts (100%) rename {instance => backend}/src/systems/settings/applicationSetting/numberSetting.ts (100%) rename {instance => backend}/src/systems/settings/applicationSetting/stringListSetting.ts (100%) rename {instance => backend}/src/systems/settings/applicationSetting/stringSetting.ts (100%) rename {instance => backend}/src/systems/terminal.ts (100%) rename {instance => backend}/src/systems/trpc.ts (76%) rename {instance => backend}/src/systems/trpcRouter.ts (91%) rename {instance => backend}/src/systems/users.ts (100%) rename {instance => backend}/src/systems/webFrontend.ts (85%) rename {instance/src/web => web}/.gitignore (100%) rename {instance/src/web => web}/deno.json (100%) rename {instance/src/web => web}/index.html (100%) rename {instance/src/web => web}/package.json (100%) rename {instance/src/web => web}/public/app.webmanifest (100%) rename {instance/src/web => web}/public/assets/generic_background.png (100%) rename {instance/src/web => web}/public/assets/generic_background.svg (100%) rename {instance/src/web => web}/public/assets/onlineworkspace/online_workspace_logo.svg (100%) rename {instance/src/web => web}/public/assets/onlineworkspace/online_workspace_wordmark.svg (100%) rename {instance/src/web => web}/public/assets/placeholder/avatar.png (100%) rename {instance/src/web => web}/public/assets/placeholder/placeholder_image.png (100%) rename {instance/src/web => web}/public/assets/placeholder/placeholder_image.svg (100%) rename {instance/src/web => web}/public/fonts/roboto.css (100%) rename {instance/src/web => web}/public/fonts/roboto_flex.css (100%) rename {instance/src/web => web}/public/fonts/roboto_flex_latin.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_flex_latin_ext.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_italic_latin.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_italic_latin_ext.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_italic_math.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_italic_symbols.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_latin.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_latin_ext.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_math.woff2 (100%) rename {instance/src/web => web}/public/fonts/roboto_symbols.woff2 (100%) rename {instance/src/web => web}/src/App.module.scss (100%) rename {instance/src/web => web}/src/App.tsx (100%) rename {instance/src/web => web}/src/MissingApp.module.scss (100%) rename {instance/src/web => web}/src/MissingApp.tsx (100%) rename {instance/src/web => web}/src/index.scss (100%) rename {instance/src/web => web}/src/index.tsx (100%) rename {instance/src/web => web}/src/lib/backend.ts (100%) rename {instance/src/web => web}/src/lib/trpc.ts (100%) rename {instance/src/web => web}/src/pages/app/AuthCheck.module.scss (100%) rename {instance/src/web => web}/src/pages/app/AuthCheck.tsx (100%) rename {instance/src/web => web}/src/pages/app/Index.tsx (100%) rename {instance/src/web => web}/src/pages/app/Layout.module.scss (100%) rename {instance/src/web => web}/src/pages/app/Layout.tsx (100%) rename {instance/src/web => web}/src/pages/app/Navigation.module.scss (100%) rename {instance/src/web => web}/src/pages/app/Navigation.tsx (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.module.scss (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.tsx (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.module.scss (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.tsx (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.module.scss (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.tsx (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.module.scss (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.tsx (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.module.scss (100%) rename {instance/src/web => web}/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.tsx (100%) rename {instance/src/web => web}/src/pages/notFound/Index.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/Layout.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/Layout.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/forgotPassword/ForgotPassword.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/forgotPassword/ForgotPassword.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/login/Login.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/login/Login.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/Signup.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/Signup.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Email/Email.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Email/Email.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Guide/Guide.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/GuidePrompt/GuidePrompt.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Password/Password.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Password/Password.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Password/components/Requirement/Requirement.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Profile/Profile.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Profile/Profile.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Username/Username.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/Username/Username.tsx (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.module.scss (100%) rename {instance/src/web => web}/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.tsx (100%) rename {instance/src/web => web}/src/vite-env.d.ts (100%) rename {instance/src/web => web}/tsconfig.app.json (83%) rename {instance/src/web => web}/tsconfig.json (100%) rename {instance/src/web => web}/tsconfig.node.json (100%) rename {instance/src/web => web}/vite.config.ts (71%) diff --git a/.run/index.ts.run.xml b/.run/index.ts.run.xml index 06bcec2..33da709 100644 --- a/.run/index.ts.run.xml +++ b/.run/index.ts.run.xml @@ -1,7 +1,7 @@ - - \ No newline at end of file + diff --git a/.vscode/launch.json b/.vscode/launch.json index 3fd353d..19cf379 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ "request": "launch", "name": "Launch Program", "type": "node", - "program": "${workspaceFolder}/instance/src/index.ts", + "program": "${workspaceFolder}/backend/src/index.ts", "cwd": "${workspaceFolder}/", "env": {}, "runtimeExecutable": "/home/ewsgit/.deno/bin/deno", diff --git a/README.md b/README.md index 5f56f19..92e2df0 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ A self-hosted web platform for applications & services with design based on Goog 11. run `systemctl enable workspaces-backend --now` to start the backend 12. change into the project root `/var/www/workspaces` 13. run `deno build-web` -14. choose a webserver of your choice to serve `/var/www/workspaces/instance/web/dist` (caddy is fast & easy to use) +14. choose a webserver of your choice to serve `/var/www/workspaces/web/dist` (caddy is fast & easy to use) 15. open your browser and head to `https://[server-ip]` and login as the user `admin` with password `password` to finish setup. --> diff --git a/instance/.gitignore b/backend/.gitignore similarity index 100% rename from instance/.gitignore rename to backend/.gitignore diff --git a/instance/deno.json b/backend/deno.json similarity index 67% rename from instance/deno.json rename to backend/deno.json index 8b55534..e097df6 100644 --- a/instance/deno.json +++ b/backend/deno.json @@ -3,7 +3,8 @@ "@std/fs": "jsr:@std/fs@^1.0.24", "@std/http": "jsr:@std/http@^1.1.1", "@std/path": "jsr:@std/path@^1.1.5", - "@opentui/core": "npm:@opentui/core@^0.4.1" + "@opentui/core": "npm:@opentui/core@^0.4.1", + "@napi-rs/canvas": "npm:@napi-rs/canvas@^1.0.0" }, "permissions": { "default": { diff --git a/instance/meta/backend.service b/backend/meta/backend.service similarity index 85% rename from instance/meta/backend.service rename to backend/meta/backend.service index 98337b9..b566687 100644 --- a/instance/meta/backend.service +++ b/backend/meta/backend.service @@ -6,7 +6,7 @@ StartLimitIntervalSec=60 [Service] Type=simple -ExecStart=/var/www/workspaces/instance/meta/backend.sh +ExecStart=/var/www/workspaces/backend/meta/backend.sh Restart=on-failure RestartSec=5 User=root diff --git a/instance/meta/backend.sh b/backend/meta/backend.sh similarity index 80% rename from instance/meta/backend.sh rename to backend/meta/backend.sh index cd7a358..d61a03c 100755 --- a/instance/meta/backend.sh +++ b/backend/meta/backend.sh @@ -5,4 +5,4 @@ cd /var/www/workspaces/ || { exit 1 } -bun ./instance/src/index.ts +bun ./backend/src/index.ts diff --git a/instance/package.json b/backend/package.json similarity index 97% rename from instance/package.json rename to backend/package.json index c4706e5..2f3e859 100644 --- a/instance/package.json +++ b/backend/package.json @@ -22,7 +22,6 @@ "@types/ws": "^8.18.1", "@types/yargs": "17.0.35", "ansi-escapes": "^7.2.0", - "canvas": "^3.2.3", "chalk": "^5.5.0", "cookies": "^0.9.1", "cors": "^2.8.5", diff --git a/instance/src/assets/fs_template_files/markdown.md b/backend/src/assets/fs_template_files/markdown.md similarity index 100% rename from instance/src/assets/fs_template_files/markdown.md rename to backend/src/assets/fs_template_files/markdown.md diff --git a/instance/src/assets/fs_template_files/plaintext.txt b/backend/src/assets/fs_template_files/plaintext.txt similarity index 100% rename from instance/src/assets/fs_template_files/plaintext.txt rename to backend/src/assets/fs_template_files/plaintext.txt diff --git a/instance/src/assets/placeholder/avatar.png b/backend/src/assets/placeholder/avatar.png similarity index 100% rename from instance/src/assets/placeholder/avatar.png rename to backend/src/assets/placeholder/avatar.png diff --git a/instance/src/assets/placeholder/banner.png b/backend/src/assets/placeholder/banner.png similarity index 100% rename from instance/src/assets/placeholder/banner.png rename to backend/src/assets/placeholder/banner.png diff --git a/instance/src/assets/placeholder/file.png b/backend/src/assets/placeholder/file.png similarity index 100% rename from instance/src/assets/placeholder/file.png rename to backend/src/assets/placeholder/file.png diff --git a/instance/src/assets/wallpapers/generic_background.png b/backend/src/assets/wallpapers/generic_background.png similarity index 100% rename from instance/src/assets/wallpapers/generic_background.png rename to backend/src/assets/wallpapers/generic_background.png diff --git a/instance/src/assets/wallpapers/pexels-argoallvee-27976588.jpg b/backend/src/assets/wallpapers/pexels-argoallvee-27976588.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-argoallvee-27976588.jpg rename to backend/src/assets/wallpapers/pexels-argoallvee-27976588.jpg diff --git a/instance/src/assets/wallpapers/pexels-eberhardgross-1624496.jpg b/backend/src/assets/wallpapers/pexels-eberhardgross-1624496.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-eberhardgross-1624496.jpg rename to backend/src/assets/wallpapers/pexels-eberhardgross-1624496.jpg diff --git a/instance/src/assets/wallpapers/pexels-jackerkun-10428721.jpg b/backend/src/assets/wallpapers/pexels-jackerkun-10428721.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-jackerkun-10428721.jpg rename to backend/src/assets/wallpapers/pexels-jackerkun-10428721.jpg diff --git a/instance/src/assets/wallpapers/pexels-jackerkun-10428723.jpg b/backend/src/assets/wallpapers/pexels-jackerkun-10428723.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-jackerkun-10428723.jpg rename to backend/src/assets/wallpapers/pexels-jackerkun-10428723.jpg diff --git a/instance/src/assets/wallpapers/pexels-jackerkun-12126032.jpg b/backend/src/assets/wallpapers/pexels-jackerkun-12126032.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-jackerkun-12126032.jpg rename to backend/src/assets/wallpapers/pexels-jackerkun-12126032.jpg diff --git a/instance/src/assets/wallpapers/pexels-steve-29708303.jpg b/backend/src/assets/wallpapers/pexels-steve-29708303.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-steve-29708303.jpg rename to backend/src/assets/wallpapers/pexels-steve-29708303.jpg diff --git a/instance/src/assets/wallpapers/pexels-tina-p-891488179-33017133.jpg b/backend/src/assets/wallpapers/pexels-tina-p-891488179-33017133.jpg similarity index 100% rename from instance/src/assets/wallpapers/pexels-tina-p-891488179-33017133.jpg rename to backend/src/assets/wallpapers/pexels-tina-p-891488179-33017133.jpg diff --git a/instance/src/assets/wallpapers/stars_wallpaper.png b/backend/src/assets/wallpapers/stars_wallpaper.png similarity index 100% rename from instance/src/assets/wallpapers/stars_wallpaper.png rename to backend/src/assets/wallpapers/stars_wallpaper.png diff --git a/instance/src/index.ts b/backend/src/index.ts similarity index 92% rename from instance/src/index.ts rename to backend/src/index.ts index 14a151a..280a72b 100644 --- a/instance/src/index.ts +++ b/backend/src/index.ts @@ -19,6 +19,8 @@ import TRPCSystem from "./systems/trpc.ts"; import { createTRPCContext as createWorkspacesTRPCContext, workspacesRouter } from "./systems/trpcRouter.ts"; import UsersSystem from "./systems/users.ts"; import WebFrontendSystem from "./systems/webFrontend.ts"; +import { routeRadix } from "@std/http/unstable-route"; +import ApiSystem from "./systems/api.ts"; export enum InstanceStatus { Online, @@ -57,6 +59,7 @@ class Instance { this.sys.email = new EmailSystem(this); this.sys.reverseProxy = new ReverseProxySystem(this); this.sys.terminal = new TerminalUISystem(this); + this.sys.api = new ApiSystem(this); this.status = InstanceStatus.Offline; } @@ -91,18 +94,10 @@ class Instance { this.sys.event.invoke(WorkspacesEvent.BeforeStartupComplete); - // this.webServer = Deno.serve(route(, () => {}) - // // this.sys.tRPC.serve({ - // // routes: { - - // // }, - // // fetch() { - // // // will be executed if it's not a TRPC request - // // return new Response("Unknown path"); - // // }, - // // development: this.sys.configuration.isDevMode, - // // }), - // ); + this.webServer = Deno.serve( + { port: this.sys.configuration.apiPort }, + routeRadix(this.sys.api.routes, () => Response.json({ failed: true })), + ); this.sys.tRPC.registeredRouters.push({ basePath: "/api/trpc", diff --git a/instance/src/log.ts b/backend/src/log.ts similarity index 100% rename from instance/src/log.ts rename to backend/src/log.ts diff --git a/instance/src/system.ts b/backend/src/system.ts similarity index 97% rename from instance/src/system.ts rename to backend/src/system.ts index 4caef63..4f20e8e 100644 --- a/instance/src/system.ts +++ b/backend/src/system.ts @@ -1,5 +1,6 @@ import type { Instance } from "./index.ts"; import type { Logger } from "./log.ts"; +import ApiSystem from "./systems/api.ts"; import type ApplicationsSubsystem from "./systems/applications.ts"; import type AuthorizationSubsystem from "./systems/authorization.ts"; import type ConfigurationSubsystem from "./systems/configuration.ts"; @@ -34,6 +35,7 @@ export type Sys = { event: EventSystem; reverseProxy: ReverseProxySystem; terminal: TerminalSystem; + api: ApiSystem; } & { [key: string]: System }; export default abstract class System { @@ -48,7 +50,6 @@ export default abstract class System { } async startup(): Promise { - this.log.info("Starting up..."); return true; } diff --git a/instance/src/systems/api.ts b/backend/src/systems/api.ts similarity index 86% rename from instance/src/systems/api.ts rename to backend/src/systems/api.ts index 8c5c5ea..0a5524c 100644 --- a/instance/src/systems/api.ts +++ b/backend/src/systems/api.ts @@ -1,274 +1,263 @@ -import { serveFile } from "@std/http/file-server"; -import type { Route } from "@std/http/unstable-route"; -import * as path from "@std/path/posix"; -import nodeCanvas from "canvas"; -import type { Instance } from "../index.ts"; -import System from "../system.ts"; - -export default class ApiSystem extends System { - private routes: Route[]; - - constructor(instance: Instance) { - super("api", instance); - - // deno-lint-ignore no-this-alias - const self = this; - - this.routes = [ - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/instance/login/banner" }), - handler(req) { - return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, "assets/login/banner.png")); - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/instance/login/background" }), - handler(req) { - return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, "assets/login/background.png")); - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/user/me/avatar/:size" }), - async handler(req, _, rawParams) { - // @ts-ignore this does exist - const params = rawParams?.pathname.groups; - - const size = params.size; - - const cookieString = req.headers?.get("cookie"); - - if (cookieString === null) { - return Response.json({ - code: "UNAUTHORIZED", - message: "missing auth cookie", - }); - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - return Response.json({ - code: "UNAUTHORIZED", - message: "invalid session", - }); - } - - switch (size) { - case "xs": - case "s": - case "m": - case "l": - case "xl": - case "2xl": - return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, `users/${userId}/assets/avatar/${size}.webp`)); - default: - return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, `users/${userId}/assets/avatar/xs.webp`)); - } - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/application/:app/icon/" }), - async handler(req, _, rawParams) { - // @ts-ignore this does exist - const params = rawParams?.pathname.groups; - - const app = (params as { app: string }).app; - - const cookieString = req.headers?.get("cookie"); - - if (cookieString === null) { - self.log.warning("Missing auth cookie in request for application icon"); - - return Response.json({ - code: "UNAUTHORIZED", - message: "missing auth cookie", - }); - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - self.log.warning("Invalid session in request for application icon"); - - return Response.json({ - code: "UNAUTHORIZED", - message: "invalid session", - }); - } - - const application = self.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === app); - - if (!application) - return Response.json({ - code: "INTERNAL_ERROR", - message: "Invalid application!", - }); - - const applicationIconPath = path.join(application.path, application.manifest?.icon?.value || ""); - - return serveFile(req, applicationIconPath); - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/asset/image/:imageId/:resolution" }), - async handler(req, _, rawParams) { - // @ts-ignore this does exist - const params = rawParams?.pathname.groups; - - const image = self.instance.sys.image._internalImages.get(params.imageId as string); - - if (!image) { - return new Response("Invalid image"); - } - - if (!image.public) { - const cookieString = req.headers?.get("cookie"); - - if (cookieString === null) { - return Response.json({ - code: "UNAUTHORIZED", - message: "missing auth cookie", - }); - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - return Response.json({ - code: "UNAUTHORIZED", - message: "invalid session", - }); - } - } - - const resolutionParam = params.resolution as string; - - const sourceImage = image[resolutionParam]; - - if (!sourceImage) { - return Response.json({ - code: "NOT_FOUND", - message: "missing image", - }); - } - - if (resolutionParam === "raw") { - self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); - return serveFile(req, sourceImage.path); - } - - return Response.json({ false: true }); - /* TODO: implement hashing again... else { - const cachedFilePath = path.join(self.instance.sys.filesystem.CACHE_PATH, sourceImage.path.replaceAll(":", "")); - const outputPath = path.join(cachedFilePath, resolutionParam); - const hashPath = path.join(`${outputPath}.hash`); - - if (fs.existsSync(outputPath)) { - const fileHash = Bun.hash.rapidhash(await Deno.readFile(sourceImage.path)).toString(); - const cacheFileHash = (await Deno.readFile(hashPath)).toString(); - - if (fileHash === cacheFileHash) { - self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); - return serveFile(req, outputPath); - } - } - - if (!fs.existsSync(path.join(outputPath, ".."))) { - await fs.ensureDir(path.join(outputPath, "..")); - } - - const fileHash = Bun.hash.rapidhash(await Deno.readFile(sourceImage.path)).toString(); - await self.instance.sys.image.resizeImage(sourceImage.path, outputPath, sourceImage.resize!.dimensions, sourceImage.resize!); - await Deno.writeFile(hashPath, Buffer.from(fileHash)); - - self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); - return serveFile(req, outputPath); - } */ - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/asset/raw/:assetId" }), - async handler(req, _, rawParams) { - // @ts-ignore this does exist - const params = rawParams?.pathname.groups; - - const asset = self.instance.sys.filesystem._internalAssets.get(params.assetId as string); - - if (!asset) { - return new Response("Invalid raw asset"); - } - - if (!asset.public) { - const cookieString = req.headers?.get("cookie"); - - if (cookieString === null) { - return Response.json({ - code: "UNAUTHORIZED", - message: "missing auth cookie", - }); - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - return Response.json({ - code: "UNAUTHORIZED", - message: "invalid session", - }); - } - } - - return serveFile(req, asset.path); - }, - }, - { - method: ["GET"], - pattern: new URLPattern({ pathname: "/api/asset/fileicon/:filetype" }), - async handler(_req, _, rawParams) { - // @ts-ignore this does exist - const params = rawParams?.pathname.groups; - - const CANVAS_PADDING = 8; - const CANVAS_WIDTH = 128; - const CANVAS_HEIGHT = 128; - const canvas = nodeCanvas.createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); - const ctx = canvas.getContext("2d"); - ctx.drawImage(await nodeCanvas.loadImage(path.join(self.instance.sys.filesystem.SRC_ROOT, "assets/placeholder/file.png")), 0, 0); - - ctx.font = `20px Arial`; - ctx.textAlign = "end"; - ctx.textBaseline = "bottom"; - const LABEL_PADDING: { x: number; y: number } = { x: 4, y: 0 }; - const labelBr: { x: number; y: number } = { x: CANVAS_WIDTH - CANVAS_PADDING, y: CANVAS_HEIGHT - CANVAS_PADDING }; - const textContent = `${params.filetype.toUpperCase().slice(0, 3)}`; - const textSize = ctx.measureText(textContent); - ctx.fillStyle = "#00aa00"; - ctx.fillRect( - labelBr.x - (textSize.width + LABEL_PADDING.x * 2), - labelBr.y - (textSize.emHeightAscent + LABEL_PADDING.y * 2), - textSize.width + LABEL_PADDING.x * 2, - textSize.emHeightAscent + LABEL_PADDING.y * 2, - ); - ctx.fillStyle = "white"; - ctx.fillText(textContent, labelBr.x - LABEL_PADDING.x, labelBr.y + LABEL_PADDING.y); - - return new Response(canvas.createPNGStream()); - }, - }, - ]; - } -} +import { serveFile } from "@std/http/file-server"; +import type { Route } from "@std/http/unstable-route"; +import * as path from "@std/path/posix"; +import nodeCanvas from "@napi-rs/canvas"; +import type { Instance } from "../index.ts"; +import System from "../system.ts"; +import { getCookies } from "@std/http"; + +export default class ApiSystem extends System { + routes: Route[]; + + constructor(instance: Instance) { + super("api", instance); + + // deno-lint-ignore no-this-alias + const self = this; + + this.routes = [ + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/instance/login/banner" }), + handler(req) { + return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, "assets/login/banner.png")); + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/instance/login/background" }), + handler(req) { + return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, "assets/login/background.png")); + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/user/me/avatar/:size" }), + async handler(req, _, rawParams) { + // @ts-ignore this does exist + const params = rawParams?.pathname.groups; + + const size = params.size; + + const cookies = getCookies(req.headers); + + if (!cookies.Authorization) { + return Response.json({ + code: "UNAUTHORIZED", + message: "missing auth cookie", + }); + } + + const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(cookies.Authorization!)); + + if (userId === undefined) { + return Response.json({ + code: "UNAUTHORIZED", + message: "invalid session", + }); + } + + switch (size) { + case "xs": + case "s": + case "m": + case "l": + case "xl": + case "2xl": + return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, `users/${userId}/assets/avatar/${size}.webp`)); + default: + return serveFile(req, path.join(self.instance.sys.filesystem.FS_ROOT, `users/${userId}/assets/avatar/xs.webp`)); + } + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/application/:app/icon/" }), + async handler(req, _, rawParams) { + // @ts-ignore this does exist + const params = rawParams?.pathname.groups; + + const app = (params as { app: string }).app; + + const cookies = getCookies(req.headers); + + if (!cookies.Authorization) { + return Response.json({ + code: "UNAUTHORIZED", + message: "missing auth cookie", + }); + } + + const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(cookies.Authorization!)); + + if (userId === undefined) { + return Response.json({ + code: "UNAUTHORIZED", + message: "invalid session", + }); + } + + const application = self.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === app); + + if (!application) + return Response.json({ + code: "INTERNAL_ERROR", + message: "Invalid application!", + }); + + const applicationIconPath = path.join(application.path, application.manifest?.icon?.value || ""); + + return serveFile(req, applicationIconPath); + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/asset/image/:imageId/:resolution" }), + async handler(req, _, rawParams) { + // @ts-ignore this does exist + const params = rawParams?.pathname.groups; + + const image = self.instance.sys.image._internalImages.get(params.imageId as string); + + if (!image) { + return new Response("Invalid image"); + } + + if (!image.public) { + const cookies = getCookies(req.headers); + + if (!cookies.Authorization) { + return Response.json({ + code: "UNAUTHORIZED", + message: "missing auth cookie", + }); + } + + const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(cookies.Authorization!)); + + if (userId === undefined) { + return Response.json({ + code: "UNAUTHORIZED", + message: "invalid session", + }); + } + } + + const resolutionParam = params.resolution as string; + + const sourceImage = image[resolutionParam]; + + if (!sourceImage) { + return Response.json({ + code: "NOT_FOUND", + message: "missing image", + }); + } + + if (resolutionParam === "raw") { + self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); + return serveFile(req, sourceImage.path); + } + + return Response.json({ false: true }); + /* TODO: implement hashing again... else { + const cachedFilePath = path.join(self.instance.sys.filesystem.CACHE_PATH, sourceImage.path.replaceAll(":", "")); + const outputPath = path.join(cachedFilePath, resolutionParam); + const hashPath = path.join(`${outputPath}.hash`); + + if (fs.existsSync(outputPath)) { + const fileHash = Bun.hash.rapidhash(await Deno.readFile(sourceImage.path)).toString(); + const cacheFileHash = (await Deno.readFile(hashPath)).toString(); + + if (fileHash === cacheFileHash) { + self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); + return serveFile(req, outputPath); + } + } + + if (!fs.existsSync(path.join(outputPath, ".."))) { + await fs.ensureDir(path.join(outputPath, "..")); + } + + const fileHash = Bun.hash.rapidhash(await Deno.readFile(sourceImage.path)).toString(); + await self.instance.sys.image.resizeImage(sourceImage.path, outputPath, sourceImage.resize!.dimensions, sourceImage.resize!); + await Deno.writeFile(hashPath, Buffer.from(fileHash)); + + self.instance.sys.image.log.info(`Served Image -> '${(params as { imageId: string }).imageId} @ ${resolutionParam}'`); + return serveFile(req, outputPath); + } */ + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/asset/raw/:assetId" }), + async handler(req, _, rawParams) { + // @ts-ignore this does exist + const params = rawParams?.pathname.groups; + + const asset = self.instance.sys.filesystem._internalAssets.get(params.assetId as string); + + if (!asset) { + return new Response("Invalid raw asset"); + } + + if (!asset.public) { + const cookies = getCookies(req.headers); + + if (!cookies.Authorization) { + return Response.json({ + code: "UNAUTHORIZED", + message: "missing auth cookie", + }); + } + + const userId = await self.instance.sys.authorization.verifySession(decodeURIComponent(cookies.Authorization!)); + + if (userId === undefined) { + return Response.json({ + code: "UNAUTHORIZED", + message: "invalid session", + }); + } + } + + return serveFile(req, asset.path); + }, + }, + { + method: ["GET"], + pattern: new URLPattern({ pathname: "/api/asset/fileicon/:filetype" }), + async handler(_req, _, rawParams) { + // @ts-ignore this does exist + const params = rawParams?.pathname.groups; + + const CANVAS_PADDING = 8; + const CANVAS_WIDTH = 128; + const CANVAS_HEIGHT = 128; + const canvas = nodeCanvas.createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); + const ctx = canvas.getContext("2d"); + ctx.drawImage(await nodeCanvas.loadImage(path.join(self.instance.sys.filesystem.SRC_ROOT, "assets/placeholder/file.png")), 0, 0); + + ctx.font = `20px Arial`; + ctx.textAlign = "end"; + ctx.textBaseline = "bottom"; + const LABEL_PADDING: { x: number; y: number } = { x: 4, y: 0 }; + const labelBr: { x: number; y: number } = { x: CANVAS_WIDTH - CANVAS_PADDING, y: CANVAS_HEIGHT - CANVAS_PADDING }; + const textContent = `${params.filetype.toUpperCase().slice(0, 3)}`; + const textSize = ctx.measureText(textContent); + ctx.fillStyle = "#00aa00"; + ctx.fillRect( + labelBr.x - (textSize.width + LABEL_PADDING.x * 2), + labelBr.y - (textSize.emHeightAscent + LABEL_PADDING.y * 2), + textSize.width + LABEL_PADDING.x * 2, + textSize.emHeightAscent + LABEL_PADDING.y * 2, + ); + ctx.fillStyle = "white"; + ctx.fillText(textContent, labelBr.x - LABEL_PADDING.x, labelBr.y + LABEL_PADDING.y); + + return new Response(canvas.createPNGStream()); + }, + }, + ]; + } +} diff --git a/instance/src/systems/applications.ts b/backend/src/systems/applications.ts similarity index 100% rename from instance/src/systems/applications.ts rename to backend/src/systems/applications.ts diff --git a/instance/src/systems/applications/application.ts b/backend/src/systems/applications/application.ts similarity index 100% rename from instance/src/systems/applications/application.ts rename to backend/src/systems/applications/application.ts diff --git a/instance/src/systems/applications/serviceStatus.ts b/backend/src/systems/applications/serviceStatus.ts similarity index 100% rename from instance/src/systems/applications/serviceStatus.ts rename to backend/src/systems/applications/serviceStatus.ts diff --git a/instance/src/systems/authorization.ts b/backend/src/systems/authorization.ts similarity index 100% rename from instance/src/systems/authorization.ts rename to backend/src/systems/authorization.ts diff --git a/instance/src/systems/commandBackup/application.ts b/backend/src/systems/commandBackup/application.ts similarity index 100% rename from instance/src/systems/commandBackup/application.ts rename to backend/src/systems/commandBackup/application.ts diff --git a/instance/src/systems/commandBackup/feature.ts b/backend/src/systems/commandBackup/feature.ts similarity index 100% rename from instance/src/systems/commandBackup/feature.ts rename to backend/src/systems/commandBackup/feature.ts diff --git a/instance/src/systems/commandBackup/install_application.ts b/backend/src/systems/commandBackup/install_application.ts similarity index 100% rename from instance/src/systems/commandBackup/install_application.ts rename to backend/src/systems/commandBackup/install_application.ts diff --git a/instance/src/systems/commandBackup/notify.ts b/backend/src/systems/commandBackup/notify.ts similarity index 100% rename from instance/src/systems/commandBackup/notify.ts rename to backend/src/systems/commandBackup/notify.ts diff --git a/instance/src/systems/commandBackup/op.ts b/backend/src/systems/commandBackup/op.ts similarity index 100% rename from instance/src/systems/commandBackup/op.ts rename to backend/src/systems/commandBackup/op.ts diff --git a/instance/src/systems/commandBackup/password.ts b/backend/src/systems/commandBackup/password.ts similarity index 100% rename from instance/src/systems/commandBackup/password.ts rename to backend/src/systems/commandBackup/password.ts diff --git a/instance/src/systems/commandBackup/restart.ts b/backend/src/systems/commandBackup/restart.ts similarity index 100% rename from instance/src/systems/commandBackup/restart.ts rename to backend/src/systems/commandBackup/restart.ts diff --git a/instance/src/systems/commandBackup/stop.ts b/backend/src/systems/commandBackup/stop.ts similarity index 100% rename from instance/src/systems/commandBackup/stop.ts rename to backend/src/systems/commandBackup/stop.ts diff --git a/instance/src/systems/commandBackup/useradd.ts b/backend/src/systems/commandBackup/useradd.ts similarity index 100% rename from instance/src/systems/commandBackup/useradd.ts rename to backend/src/systems/commandBackup/useradd.ts diff --git a/instance/src/systems/commandBackup/userdel.ts b/backend/src/systems/commandBackup/userdel.ts similarity index 100% rename from instance/src/systems/commandBackup/userdel.ts rename to backend/src/systems/commandBackup/userdel.ts diff --git a/instance/src/systems/commandBackup/users.ts b/backend/src/systems/commandBackup/users.ts similarity index 100% rename from instance/src/systems/commandBackup/users.ts rename to backend/src/systems/commandBackup/users.ts diff --git a/instance/src/systems/configuration.ts b/backend/src/systems/configuration.ts similarity index 89% rename from instance/src/systems/configuration.ts rename to backend/src/systems/configuration.ts index 4545369..98fd839 100644 --- a/instance/src/systems/configuration.ts +++ b/backend/src/systems/configuration.ts @@ -27,6 +27,14 @@ export default class ConfigurationSystem extends System { port: number; database: string; }; + } = { + postgres: { + user: "postgres", + password: "postgres", + host: "localhost", + port: 5432, + database: "onlineworkspace_workspace", + }, }; proxyUrl: string; enabledFeatures: (WorkspacesFeatureFlags | string)[]; @@ -40,6 +48,16 @@ export default class ConfigurationSystem extends System { minimumNumbers?: number; minimumSymbols?: number; }; + } = { + email: false, + twoFactorAuthentication: false, + passwordMinimumLength: 5, + passwordContains: { + minimumLowercase: 1, + minimumNumbers: 1, + minimumSymbols: 1, + minimumUppercase: 1, + }, }; displayName: string; mailServer: { @@ -52,36 +70,26 @@ export default class ConfigurationSystem extends System { }; }; termsOfUse: { message: string; lastUpdated: number }; - defaultQuickShortcuts: string[]; + defaultQuickShortcuts: string[] = ["uk.ewsgit.dashboard", "uk.ewsgit.store", "uk.ewsgit.settings", "uk.ewsgit.photos", "uk.ewsgit.files"]; defaultApplications: { id: string; uri: string }[]; + userDefault: { + homeDirectories: string[]; + quotaSize: number; + displayNameFormat: string; + } = { + homeDirectories: ["Documents", "Photos", "Videos", "Projects"], + quotaSize: 1024 * 1024 * 10, + displayNameFormat: "New User %num%", + }; caddyfile: string | undefined; + apiPort: number = 3563; constructor(instance: Instance) { super("configuration", instance); this.enabledFeatures = [WorkspacesFeatureFlags.SlashCommands]; - this.databases = { - postgres: { - user: "postgres", - password: "postgres", - host: "localhost", - port: 5432, - database: "onlineworkspace_workspace", - }, - }; if (process.env.POSTGRES_DATABASE_HOST) this.databases.postgres.host = process.env.POSTGRES_DATABASE_HOST; this.proxyUrl = "https://localhost"; - this.signupRequirements = { - email: false, - twoFactorAuthentication: false, - passwordMinimumLength: 5, - passwordContains: { - minimumLowercase: 1, - minimumNumbers: 1, - minimumSymbols: 1, - minimumUppercase: 1, - }, - }; this.displayName = "Workspace"; this.mailServer = { host: "smtp.example.com", @@ -113,7 +121,6 @@ export default class ConfigurationSystem extends System { - These terms may change. If we make significant updates, we will post a notification within the app or send an email.`, lastUpdated: Date.now(), }; - this.defaultQuickShortcuts = ["uk.ewsgit.dashboard", "uk.ewsgit.store", "uk.ewsgit.settings", "uk.ewsgit.photos", "uk.ewsgit.files"]; this.defaultApplications = [ { id: "uk.ewsgit.dashboard", uri: "local:uk.ewsgit.dashboard" }, { id: "uk.ewsgit.store", uri: "local:uk.ewsgit.store" }, diff --git a/instance/src/systems/consoleCommands.ts b/backend/src/systems/consoleCommands.ts similarity index 100% rename from instance/src/systems/consoleCommands.ts rename to backend/src/systems/consoleCommands.ts diff --git a/instance/src/systems/consoleCommands/application.ts b/backend/src/systems/consoleCommands/application.ts similarity index 100% rename from instance/src/systems/consoleCommands/application.ts rename to backend/src/systems/consoleCommands/application.ts diff --git a/instance/src/systems/consoleCommands/exit.ts b/backend/src/systems/consoleCommands/exit.ts similarity index 100% rename from instance/src/systems/consoleCommands/exit.ts rename to backend/src/systems/consoleCommands/exit.ts diff --git a/instance/src/systems/database.ts b/backend/src/systems/database.ts similarity index 100% rename from instance/src/systems/database.ts rename to backend/src/systems/database.ts diff --git a/instance/src/systems/email.ts b/backend/src/systems/email.ts similarity index 100% rename from instance/src/systems/email.ts rename to backend/src/systems/email.ts diff --git a/instance/src/systems/events.ts b/backend/src/systems/events.ts similarity index 100% rename from instance/src/systems/events.ts rename to backend/src/systems/events.ts diff --git a/instance/src/systems/filesystem.ts b/backend/src/systems/filesystem.ts similarity index 96% rename from instance/src/systems/filesystem.ts rename to backend/src/systems/filesystem.ts index ab55dd5..4d9c667 100644 --- a/instance/src/systems/filesystem.ts +++ b/backend/src/systems/filesystem.ts @@ -1,241 +1,241 @@ -import fs from "node:fs"; -import path from "node:path"; -import type { Instance } from "../index.ts"; -import System from "../system.ts"; -import { WorkspacesEvent } from "./events.ts"; - -export default class FilesystemSystem extends System { - readonly SRC_ROOT = path.resolve("./instance/src/"); - readonly FS_ROOT = path.resolve("./fs/"); - readonly CACHE_PATH = path.join(this.FS_ROOT, "cache"); - readonly SYSTEM_PATH = path.join(this.FS_ROOT, "system"); - readonly AUTOINSTALL_PATH = path.join(process.cwd(), "autoinstall"); - - _internalAssets: Map; - _internalAssetPaths: Map; - - constructor(instance: Instance) { - super("filesystem", instance); - - if (!fs.existsSync(this.FS_ROOT)) { - fs.mkdirSync(this.FS_ROOT, { recursive: true }); - } - - if (!fs.existsSync(this.SYSTEM_PATH)) { - fs.mkdirSync(this.SYSTEM_PATH, { - recursive: true, - }); - } - - if (!fs.existsSync(this.CACHE_PATH)) { - fs.mkdirSync(this.CACHE_PATH, { recursive: true }); - } - - if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login"))) { - fs.mkdirSync(path.join(this.FS_ROOT, "assets/login"), { - recursive: true, - }); - } - - if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login/banner.png"))) { - if (!fs.existsSync(path.join(this.AUTOINSTALL_PATH, "assets/login/banner.png"))) { - fs.cpSync(path.join(this.SRC_ROOT, "assets/placeholder/banner.png"), path.join(this.FS_ROOT, "assets/login/banner.png")); - } else { - fs.cpSync(path.join(this.AUTOINSTALL_PATH, "assets/login/banner.png"), path.join(this.FS_ROOT, "assets/login/banner.png")); - } - } - - if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login/background.png"))) { - fs.cpSync(path.join(this.SRC_ROOT, "assets/wallpapers/pexels-steve-29708303.jpg"), path.join(this.FS_ROOT, "assets/login/background.png")); - } - - if (!fs.existsSync(path.join(this.SYSTEM_PATH, "fs_template_files"))) { - fs.mkdirSync(path.join(this.SYSTEM_PATH, "fs_template_files")); - fs.cpSync(path.join(this.SRC_ROOT, "assets/fs_template_files/"), path.join(this.SYSTEM_PATH, "fs_template_files"), { recursive: true }); - } - - if (!fs.existsSync(path.join(this.SYSTEM_PATH, "vite"))) { - fs.mkdirSync(path.join(this.SYSTEM_PATH, "vite")); - } - - this._internalAssets = new Map(); - this._internalAssetPaths = new Map(); - } - - getApplicationSrc(applicationId: string) { - console.log(this.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === applicationId)?.path); - - return this.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === applicationId)?.path; - } - - // Create a directory if it does not already exist - // @returns {true} if created - // @returns {false} if already exists - async createDirectoryIfNotExists(path: string): Promise { - if ( - await fs.promises - .access(path) - .then(() => true) - .catch(() => false) - ) { - return false; - } - - await fs.promises.mkdir(path, { recursive: true }); - - return true; - } - - getFileType(path: string) { - const extension = path.split(".").pop(); - - switch (extension) { - case "avif": - case "webp": - case "jpg": - case "jpeg": - case "png": - case "gif": - return "image"; - case "txt": - case "json": - case "js": - case "ts": - case "tsx": - case "scss": - case "sass": - case "yml": - case "yaml": - case "xml": - case "py": - case "toml": - case "rs": - case "html": - case "htm": - case "css": - case "jsm": - case "tsm": - case "tsc": - case "jsc": - case "sql": - case "lock": - return "plaintext"; - } - - this.log.warning(`Unknown file format: '${path}' ext: '${extension}'`); - - return undefined; - } - - // returns an endpoint where the asset located at the provided path can be loaded from on the client - // defaults to 3hrs validity - serveFile(userId: number, fsPath: string, isPublic: boolean = false, dontCachePath = false, validMs: number = 21600000): string { - if (!dontCachePath) { - const existingAsset = this._internalAssetPaths.get(fsPath); - - if (existingAsset) { - this._internalAssets.get(existingAsset)!.validUntil = Date.now() + validMs; - - return `/api/asset/raw/${existingAsset}`; - } - } - - const assetId = crypto.randomUUID(); - - this._internalAssets.set(assetId, { - path: fsPath, - userId: userId, - validUntil: Date.now() + validMs, - public: isPublic, - }); - this._internalAssetPaths.set(fsPath, assetId); - - this.log.info(`Serving asset at '${fsPath}' as '${assetId}'`); - - return `/api/asset/raw/${assetId}`; - } - - // TODO: properly implement this instead of this hack. - async getUserPermissions(userId: number, fsPath: string): Promise<{ read: boolean; write: boolean }> { - const user = (await this.instance.sys.users.getUserById(userId))!; - - if (await user.isAdministrator()) - return { - read: true, - write: true, - }; - - if (fsPath.startsWith(path.join(this.FS_ROOT, `/users/${userId}`))) - return { - read: true, - write: true, - }; - - const db = this.instance.sys.database.postgres(); - - const userGroups = (await user.getGroups()) || []; - const userIdArray = userId ? [userId.toString()] : []; - - const validUserGroups = userGroups.filter(Boolean); - - return { - read: false, - write: false, - }; - - // const permissions = await db` - // SELECT - // EXISTS( - // SELECT 1 - // FROM public.filesystem_permission_overrides - // WHERE file_path = ${fsPath} - // AND read_permission_groups && ${validUserGroups}::TEXT[] - // OR read_permission_users @> ARRAY[${userIdArray}::TEXT] - // ) AS read_permission, - // EXISTS( - // SELECT 1 - // FROM public.filesystem_permission_overrides - // WHERE file_path = ${fsPath} - // AND write_permission_groups && ${validUserGroups}::TEXT[] - // OR write_permission_users @> ARRAY[${userIdArray}::TEXT] - // ) AS write_permission - // `; - // return { - // read: permissions[0].read_permission, - // write: permissions[0].write_permission, - // }; - } - - getUserHomeDirectory(userId: number): string { - return path.join(this.FS_ROOT, `/users/${userId}`); - } - - override async startup(): Promise { - await super.startup(); - - this.instance.sys.event.on(WorkspacesEvent.BeforeStartupComplete, async () => { - const db = this.instance.sys.database.postgres(); - - await db`CREATE TABLE IF NOT EXISTS filesystem_permission_overrides ( - file_path TEXT, - recursive BOOL, - read_permission_groups TEXT[], - read_permission_users TEXT[], - write_permission_groups TEXT[], - write_permission_users TEXT[], - owner TEXT[] -)`; - - if (!this.instance.sys.configuration.isDevMode) { - if (fs.existsSync(this.CACHE_PATH)) { - fs.rmSync(this.CACHE_PATH, { recursive: true }); - fs.mkdirSync(this.CACHE_PATH, { recursive: true }); - } - } else { - this.log.warning("Cache was not cleared as we are running in devMode"); - } - }); - - return true; - } -} +import fs from "node:fs"; +import path from "node:path"; +import type { Instance } from "../index.ts"; +import System from "../system.ts"; +import { WorkspacesEvent } from "./events.ts"; + +export default class FilesystemSystem extends System { + readonly SRC_ROOT = path.resolve("./backend/src/"); + readonly FS_ROOT = path.resolve("./fs/"); + readonly CACHE_PATH = path.join(this.FS_ROOT, "cache"); + readonly SYSTEM_PATH = path.join(this.FS_ROOT, "system"); + readonly AUTOINSTALL_PATH = path.join(process.cwd(), "autoinstall"); + + _internalAssets: Map; + _internalAssetPaths: Map; + + constructor(instance: Instance) { + super("filesystem", instance); + + if (!fs.existsSync(this.FS_ROOT)) { + fs.mkdirSync(this.FS_ROOT, { recursive: true }); + } + + if (!fs.existsSync(this.SYSTEM_PATH)) { + fs.mkdirSync(this.SYSTEM_PATH, { + recursive: true, + }); + } + + if (!fs.existsSync(this.CACHE_PATH)) { + fs.mkdirSync(this.CACHE_PATH, { recursive: true }); + } + + if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login"))) { + fs.mkdirSync(path.join(this.FS_ROOT, "assets/login"), { + recursive: true, + }); + } + + if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login/banner.png"))) { + if (!fs.existsSync(path.join(this.AUTOINSTALL_PATH, "assets/login/banner.png"))) { + fs.cpSync(path.join(this.SRC_ROOT, "assets/placeholder/banner.png"), path.join(this.FS_ROOT, "assets/login/banner.png")); + } else { + fs.cpSync(path.join(this.AUTOINSTALL_PATH, "assets/login/banner.png"), path.join(this.FS_ROOT, "assets/login/banner.png")); + } + } + + if (!fs.existsSync(path.join(this.FS_ROOT, "assets/login/background.png"))) { + fs.cpSync(path.join(this.SRC_ROOT, "assets/wallpapers/pexels-steve-29708303.jpg"), path.join(this.FS_ROOT, "assets/login/background.png")); + } + + if (!fs.existsSync(path.join(this.SYSTEM_PATH, "fs_template_files"))) { + fs.mkdirSync(path.join(this.SYSTEM_PATH, "fs_template_files")); + fs.cpSync(path.join(this.SRC_ROOT, "assets/fs_template_files/"), path.join(this.SYSTEM_PATH, "fs_template_files"), { recursive: true }); + } + + if (!fs.existsSync(path.join(this.SYSTEM_PATH, "vite"))) { + fs.mkdirSync(path.join(this.SYSTEM_PATH, "vite")); + } + + this._internalAssets = new Map(); + this._internalAssetPaths = new Map(); + } + + getApplicationSrc(applicationId: string) { + console.log(this.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === applicationId)?.path); + + return this.instance.sys.applications.availableApplications.find((a) => a.manifest?.id === applicationId)?.path; + } + + // Create a directory if it does not already exist + // @returns {true} if created + // @returns {false} if already exists + async createDirectoryIfNotExists(path: string): Promise { + if ( + await fs.promises + .access(path) + .then(() => true) + .catch(() => false) + ) { + return false; + } + + await fs.promises.mkdir(path, { recursive: true }); + + return true; + } + + getFileType(path: string) { + const extension = path.split(".").pop(); + + switch (extension) { + case "avif": + case "webp": + case "jpg": + case "jpeg": + case "png": + case "gif": + return "image"; + case "txt": + case "json": + case "js": + case "ts": + case "tsx": + case "scss": + case "sass": + case "yml": + case "yaml": + case "xml": + case "py": + case "toml": + case "rs": + case "html": + case "htm": + case "css": + case "jsm": + case "tsm": + case "tsc": + case "jsc": + case "sql": + case "lock": + return "plaintext"; + } + + this.log.warning(`Unknown file format: '${path}' ext: '${extension}'`); + + return undefined; + } + + // returns an endpoint where the asset located at the provided path can be loaded from on the client + // defaults to 3hrs validity + serveFile(userId: number, fsPath: string, isPublic: boolean = false, dontCachePath = false, validMs: number = 21600000): string { + if (!dontCachePath) { + const existingAsset = this._internalAssetPaths.get(fsPath); + + if (existingAsset) { + this._internalAssets.get(existingAsset)!.validUntil = Date.now() + validMs; + + return `/api/asset/raw/${existingAsset}`; + } + } + + const assetId = crypto.randomUUID(); + + this._internalAssets.set(assetId, { + path: fsPath, + userId: userId, + validUntil: Date.now() + validMs, + public: isPublic, + }); + this._internalAssetPaths.set(fsPath, assetId); + + this.log.info(`Serving asset at '${fsPath}' as '${assetId}'`); + + return `/api/asset/raw/${assetId}`; + } + + // TODO: properly implement this instead of this hack. + async getUserPermissions(userId: number, fsPath: string): Promise<{ read: boolean; write: boolean }> { + const user = (await this.instance.sys.users.getUserById(userId))!; + + if (await user.isAdministrator()) + return { + read: true, + write: true, + }; + + if (fsPath.startsWith(path.join(this.FS_ROOT, `/users/${userId}`))) + return { + read: true, + write: true, + }; + + const db = this.instance.sys.database.postgres(); + + const userGroups = (await user.getGroups()) || []; + const userIdArray = userId ? [userId.toString()] : []; + + const validUserGroups = userGroups.filter(Boolean); + + return { + read: false, + write: false, + }; + + // const permissions = await db` + // SELECT + // EXISTS( + // SELECT 1 + // FROM public.filesystem_permission_overrides + // WHERE file_path = ${fsPath} + // AND read_permission_groups && ${validUserGroups}::TEXT[] + // OR read_permission_users @> ARRAY[${userIdArray}::TEXT] + // ) AS read_permission, + // EXISTS( + // SELECT 1 + // FROM public.filesystem_permission_overrides + // WHERE file_path = ${fsPath} + // AND write_permission_groups && ${validUserGroups}::TEXT[] + // OR write_permission_users @> ARRAY[${userIdArray}::TEXT] + // ) AS write_permission + // `; + // return { + // read: permissions[0].read_permission, + // write: permissions[0].write_permission, + // }; + } + + getUserHomeDirectory(userId: number): string { + return path.join(this.FS_ROOT, `/users/${userId}`); + } + + override async startup(): Promise { + await super.startup(); + + this.instance.sys.event.on(WorkspacesEvent.BeforeStartupComplete, async () => { + const db = this.instance.sys.database.postgres(); + + await db`CREATE TABLE IF NOT EXISTS filesystem_permission_overrides ( + file_path TEXT, + recursive BOOL, + read_permission_groups TEXT[], + read_permission_users TEXT[], + write_permission_groups TEXT[], + write_permission_users TEXT[], + owner TEXT[] +)`; + + if (!this.instance.sys.configuration.isDevMode) { + if (fs.existsSync(this.CACHE_PATH)) { + fs.rmSync(this.CACHE_PATH, { recursive: true }); + fs.mkdirSync(this.CACHE_PATH, { recursive: true }); + } + } else { + this.log.warning("Cache was not cleared as we are running in devMode"); + } + }); + + return true; + } +} diff --git a/instance/src/systems/image.ts b/backend/src/systems/image.ts similarity index 100% rename from instance/src/systems/image.ts rename to backend/src/systems/image.ts diff --git a/instance/src/systems/notifications.ts b/backend/src/systems/notifications.ts similarity index 100% rename from instance/src/systems/notifications.ts rename to backend/src/systems/notifications.ts diff --git a/instance/src/systems/reverseProxy.ts b/backend/src/systems/reverseProxy.ts similarity index 100% rename from instance/src/systems/reverseProxy.ts rename to backend/src/systems/reverseProxy.ts diff --git a/instance/src/systems/settings.ts b/backend/src/systems/settings.ts similarity index 100% rename from instance/src/systems/settings.ts rename to backend/src/systems/settings.ts diff --git a/instance/src/systems/settings/applicationSetting/applicationSetting.ts b/backend/src/systems/settings/applicationSetting/applicationSetting.ts similarity index 100% rename from instance/src/systems/settings/applicationSetting/applicationSetting.ts rename to backend/src/systems/settings/applicationSetting/applicationSetting.ts diff --git a/instance/src/systems/settings/applicationSetting/booleanSetting.ts b/backend/src/systems/settings/applicationSetting/booleanSetting.ts similarity index 100% rename from instance/src/systems/settings/applicationSetting/booleanSetting.ts rename to backend/src/systems/settings/applicationSetting/booleanSetting.ts diff --git a/instance/src/systems/settings/applicationSetting/numberSetting.ts b/backend/src/systems/settings/applicationSetting/numberSetting.ts similarity index 100% rename from instance/src/systems/settings/applicationSetting/numberSetting.ts rename to backend/src/systems/settings/applicationSetting/numberSetting.ts diff --git a/instance/src/systems/settings/applicationSetting/stringListSetting.ts b/backend/src/systems/settings/applicationSetting/stringListSetting.ts similarity index 100% rename from instance/src/systems/settings/applicationSetting/stringListSetting.ts rename to backend/src/systems/settings/applicationSetting/stringListSetting.ts diff --git a/instance/src/systems/settings/applicationSetting/stringSetting.ts b/backend/src/systems/settings/applicationSetting/stringSetting.ts similarity index 100% rename from instance/src/systems/settings/applicationSetting/stringSetting.ts rename to backend/src/systems/settings/applicationSetting/stringSetting.ts diff --git a/instance/src/systems/terminal.ts b/backend/src/systems/terminal.ts similarity index 100% rename from instance/src/systems/terminal.ts rename to backend/src/systems/terminal.ts diff --git a/instance/src/systems/trpc.ts b/backend/src/systems/trpc.ts similarity index 76% rename from instance/src/systems/trpc.ts rename to backend/src/systems/trpc.ts index 4d14b75..e780081 100644 --- a/instance/src/systems/trpc.ts +++ b/backend/src/systems/trpc.ts @@ -1,15 +1,13 @@ import type { TRPCBuiltRouter } from "@trpc/server"; import { type FetchCreateContextFnOptions, fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import type { BunRequest, Server } from "bun"; import type { Instance } from "../index.ts"; import System from "../system.ts"; -import type { createTRPCContext } from "./trpcRouter.ts"; export default class TRPCSystem extends System { registeredRouters: { basePath: string; router: TRPCBuiltRouter; - createContext: (opts: FetchCreateContextFnOptions, server: Server>) => object; + createContext: (opts: FetchCreateContextFnOptions, server: Deno.HttpServer) => object; }[]; constructor(instance: Instance) { @@ -23,7 +21,7 @@ export default class TRPCSystem extends System { return true; } - private attemptTRPCRequest(req: BunRequest, server: Server>) { + private attemptTRPCRequest(req: Request, server: Deno.HttpServer) { const url = new URL(req.url); for (const router of this.registeredRouters) { @@ -47,10 +45,10 @@ export default class TRPCSystem extends System { serve(options: { routes: { [path: string]: { - GET?: (req: BunRequest) => Promise; - POST?: (req: BunRequest) => Promise; - DELETE?: (req: BunRequest) => Promise; - PUT?: (req: BunRequest) => Promise; + GET?: (req: Request) => Promise; + POST?: (req: Request) => Promise; + DELETE?: (req: Request) => Promise; + PUT?: (req: Request) => Promise; }; }; fetch(request: any, server: any): Response; @@ -62,7 +60,7 @@ export default class TRPCSystem extends System { ...options, port: 3563, hostname: "0.0.0.0", - async fetch(req: BunRequest, server: Server>) { + async fetch(req: Request, server: Deno.HttpServer) { try { const trpcResponse = await self.attemptTRPCRequest(req, server); if (trpcResponse) { diff --git a/instance/src/systems/trpcRouter.ts b/backend/src/systems/trpcRouter.ts similarity index 91% rename from instance/src/systems/trpcRouter.ts rename to backend/src/systems/trpcRouter.ts index 80b4db3..e7620d1 100644 --- a/instance/src/systems/trpcRouter.ts +++ b/backend/src/systems/trpcRouter.ts @@ -1,745 +1,731 @@ -import * as nodeCrypto from "node:crypto"; -import { on } from "node:events"; -import { initTRPC, TRPCError } from "@trpc/server"; -import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; -import type { Server } from "bun"; -import * as hiBase32 from "hi-base32"; -import * as OTPAuth from "otpauth"; -import z from "zod"; -import type { Instance } from "../index.ts"; -import { AuthorizedDeviceType } from "./authorization.ts"; -import { WorkspacesFeatureFlags } from "./configuration.ts"; -import { type WorkspacesNotification, WorkspacesNotificationEventEmitterEvent } from "./notifications.ts"; -import type { WorkspacesUser } from "./users.ts"; - -export const createTRPCContext = (instance: Instance) => (opt: FetchCreateContextFnOptions, server: Server) => { - return { - rawRequest: { - req: opt.req, - resHeaders: opt.resHeaders, - server: server, - }, - instance: instance, - }; -}; - -export const t = initTRPC.context>().create({ - sse: { - ping: { - // Enable periodic ping messages to keep connection alive - enabled: true, - // Send ping message every 2s - intervalMs: 4000, - }, - client: { - reconnectAfterInactivityMs: 5000, - }, - }, -}); - -export const publicProcedure = t.procedure.use(async (opt) => { - return opt.next({ - ctx: { - userId: "THIS CAN ONLY BE ACCESSED FROM A NON-PUBLIC PROCEDURE", - }, - }); -}); -export const procedure = t.procedure.use(async (opt) => { - // console.log(opt.ctx.req.headers); - - /* - if (!ctx.user?.isAdmin) { - throw new TRPCError({ code: 'UNAUTHORIZED' }); - } - */ - - const cookieString = opt.ctx.rawRequest.req.headers?.get("cookie"); - - if (cookieString === null) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "missing auth cookie", - }); - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await opt.ctx.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - throw new TRPCError({ code: "UNAUTHORIZED", message: "invalid session" }); - } - - return opt.next({ - ctx: { - userId: userId, - user: (): Promise => - // @ts-ignore - opt.ctx.instance.sys.users.getUserById(userId), - }, - }); -}); -export const adminProcedure = procedure.use(async (opt) => { - const user = await opt.ctx.instance.sys.users.getUserById(opt.ctx.userId); - - if (!user) { - throw new TRPCError({ code: "UNAUTHORIZED", message: "invalid session" }); - } - - if (!(await user.isAdministrator())) { - throw new TRPCError({ - code: "UNAUTHORIZED", - message: "user lacks administrator permissions", - }); - } - - return opt.next(); -}); - -const temporaryTwoFactorSecrets: Map = new Map(); -const emailSignupVerificationCodes: Map = new Map(); -const notifications: WorkspacesNotification[] = []; - -export const workspacesRouter = t.router({ - authorization: { - signupRequirements: publicProcedure - .output( - z.object({ - email: z.boolean(), - twoFactorAuthentication: z.boolean(), - passwordMinimumLength: z.number().optional(), - passwordContains: z - .object({ - minimumUppercase: z.number().optional(), - minimumLowercase: z.number().optional(), - minimumNumbers: z.number().optional(), - minimumSymbols: z.number().optional(), - }) - .optional(), - }), - ) - .query(async (opt) => { - return opt.ctx.instance.sys.configuration.signupRequirements; - }), - checkEmailAddressOwnership: publicProcedure - .input(z.object({ emailAddress: z.string() })) - .output(z.boolean().or(z.string())) - .mutation(async (opt) => { - if (emailSignupVerificationCodes.has(opt.input.emailAddress)) { - return "An email has already been sent, please wait 5 minutes before sending another."; - } - - let emailCode = ""; - const CODE_LENGTH = 8; - const CODE_VALID_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - for (let i = CODE_LENGTH; i > 0; --i) emailCode += CODE_VALID_CHARS[Math.floor(Math.random() * CODE_VALID_CHARS.length)]; - - emailSignupVerificationCodes.set(opt.input.emailAddress, emailCode); - // TODO: send an email... - opt.ctx.instance.log.system.info(`Email code for email '${opt.input.emailAddress}' is '${emailCode}'`); - - return true; - }), - validateEmailCode: publicProcedure.input(z.object({ emailAddress: z.string(), emailCode: z.string() })).query(async (opt) => { - if (emailSignupVerificationCodes.get(opt.input.emailAddress) === opt.input.emailCode) { - return true; - } - - return false; - }), - isUsernameValid: publicProcedure.input(z.string()).query(async (opt) => { - if ((await opt.ctx.instance.sys.users.getUserByUsername(opt.input)) === undefined) return true; - - return false; - }), - canSignup: publicProcedure.query(async (opt) => { - if (opt.ctx.instance.sys.configuration.hasFeature(WorkspacesFeatureFlags.AllowUserSignups)) return true; - - return false; - }), - signup: publicProcedure - .input( - z.union([ - z.object({ - username: z.string(), - password: z.string(), - emailAddress: z.string(), - emailCode: z.string(), - displayName: z.string(), - gender: z.string(), - bio: z.string(), - }), - z.object({ - username: z.string(), - password: z.string(), - displayName: z.string(), - gender: z.string(), - bio: z.string(), - }), - ]), - ) - .output( - z.union([ - z.object({ type: z.literal("error"), message: z.string() }), - z.object({ - type: z.literal("success"), - sessionToken: z.string(), - notice: z.boolean().optional(), - }), - ]), - ) - .mutation(async (opt) => { - if (!opt.ctx.instance.sys.configuration.hasFeature(WorkspacesFeatureFlags.AllowUserSignups)) - return { - type: "error", - message: "This instance has disabled user signups", - }; - - const username = opt.input.username.toLowerCase(); - - if (opt.ctx.instance.sys.configuration.signupRequirements.email) { - if (!("emailAddress" in opt.input)) { - return { - type: "error", - message: "This instance requires an email address for signups!", - }; - } - - if (opt.input.emailCode !== emailSignupVerificationCodes.get(opt.input.emailAddress)) - return { - type: "error", - message: "The email code did not match!", - }; - } - - const uid = await opt.ctx.instance.sys.users.createUser(username, opt.input.password); - - if (uid === undefined) - return { - type: "error", - message: "Failed to create the user", - }; - - const user = await opt.ctx.instance.sys.users.getUserById(uid); - - if (user === undefined) - return { - type: "error", - message: "Failed to fetch the user", - }; - - const splitDisplayName = opt.input.displayName.split(" "); - await user.setFullName(splitDisplayName[0], splitDisplayName.slice(1).join(" ")); - - if ("emailAddress" in opt.input) { - await user.setEmail(opt.input.emailAddress); - } - - await user.setBio(opt.input.bio); - - if (opt.input.gender === "male" || opt.input.gender === "female" || opt.input.gender === "other") await user.setGender(opt.input.gender); - - // TODO: get default quota size from the instance config - await user.setQuota(20); - - const session = await opt.ctx.instance.sys.authorization.createPasswordSession( - user.userId, - opt.input.password, - AuthorizedDeviceType.UnknownBrowser, - undefined, - opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", - ); - - if (session === undefined) { - return { - type: "error", - message: "Failed to create a session?", - }; - } - - opt.ctx.rawRequest.resHeaders.set( - "set-cookie", - Bun.Cookie.from("Authorization", session, { - secure: false, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14).toUTCString(), - }).serialize(), - ); - - return { - type: "success", - sessionToken: session, - }; - }), - confirmTwoFactor: procedure.input(z.object({ twoFactorCode: z.string() })).mutation(async (opt) => { - const user = await opt.ctx.user(); - const secretString = temporaryTwoFactorSecrets.get(user.userId); - - if (secretString === undefined) { - opt.ctx.instance.log.system.warning( - `(${user.userId})${await user.getUsername()} Tried to confirm a two factor code, but they lacked a temporary secret?`, - ); - - return false; - } - - const totp = new OTPAuth.TOTP({ - issuer: opt.ctx.instance.sys.configuration.proxyUrl[0], - label: `${opt.ctx.instance.sys.configuration.displayName} (Workspace)`, - algorithm: "SHA1", - digits: 6, - secret: secretString, - }); - - if (totp.validate({ token: opt.input.twoFactorCode }) !== null) { - temporaryTwoFactorSecrets.delete(user.userId); - await opt.ctx.instance.sys.authorization.setTwoFactorAuthenticationSecret(user.userId, secretString); - opt.ctx.instance.log.system.success(`(${user.userId})${await user.getUsername()} Setup two-factor authentication on their account!`); - - return true; - } - - return false; - }), - enableTwoFactor: procedure - .output( - z - .object({ - twoFactorSecret: z.string(), - twoFactorSecretURI: z.string(), - }) - .optional(), - ) - .mutation(async (opt) => { - const generateSecretString = () => { - const buffer = nodeCrypto.randomBytes(15); - const base32 = hiBase32.encode(buffer).replace(/=/g, "").substring(0, 24); - return base32; - }; - - const user = await opt.ctx.user(); - - if (await opt.ctx.instance.sys.authorization.hasTwoFactorAuthenticationSecret(user.userId)) { - opt.ctx.instance.log.system.warning( - `User (${user.userId})${await user.getUsername()} has attempted to re-setup their two factor from the signup method... this is suspicious...`, - ); - - return undefined; - } - - let secretString = generateSecretString(); - const prevSecretString = temporaryTwoFactorSecrets.get(user.userId); - - if (prevSecretString) { - secretString = prevSecretString; - } - - const totp = new OTPAuth.TOTP({ - issuer: opt.ctx.instance.sys.configuration.proxyUrl[0], - label: `${opt.ctx.instance.sys.configuration.displayName} (Workspace)`, - algorithm: "SHA1", - digits: 6, - secret: secretString, - }); - - temporaryTwoFactorSecrets.set(user.userId, secretString); - - return { - twoFactorSecretURI: totp.toString(), - twoFactorSecret: secretString, - }; - }), - passwordSignin: publicProcedure - .input( - z.object({ - username: z.string(), - password: z.string(), - twoFactorCode: z.string().optional(), - }), - ) - .output( - z.union([ - z.object({ type: z.literal("error"), message: z.string() }), - z.object({ type: z.literal("success"), sessionToken: z.string() }), - z.object({ type: z.literal("twofactor") }), - ]), - ) - .mutation(async (opt) => { - const username = opt.input.username.toLowerCase(); - const user = await opt.ctx.instance.sys.users.getUserByUsername(username); - - if (user === undefined) - return { - type: "error", - message: "Failed to find the user", - }; - - if (await opt.ctx.instance.sys.authorization.hasTwoFactorAuthenticationSecret(user.userId)) { - if (opt.input.twoFactorCode === undefined) - return { - type: "twofactor", - }; - } - - const session = await opt.ctx.instance.sys.authorization.createPasswordSession( - user.userId, - opt.input.password, - AuthorizedDeviceType.UnknownBrowser, - opt.input.twoFactorCode, - opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", - ); - - if (session === undefined) { - return { - type: "error", - message: "Failed to create a session?", - }; - } - - opt.ctx.rawRequest.resHeaders.set( - "set-cookie", - Bun.Cookie.from("Authorization", session, { - secure: false, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14).toUTCString(), - }).serialize(), - ); - - return { - type: "success", - sessionToken: session, - }; - }), - passkeyRequestSignin: publicProcedure - .input( - z.object({ - username: z.string(), - }), - ) - .query(async (opt) => { - const user = await opt.ctx.instance.sys.users.getUserByUsername(opt.input.username.toLowerCase()); - - if (user === undefined) { - return undefined; - } - - return opt.ctx.instance.sys.authorization.requestPasskeySession(user.userId); - }), - passkeyCompleteSignin: publicProcedure - .input( - z.object({ - username: z.string(), - passkeyResponse: z.any(), - }), - ) - .mutation(async (opt) => { - const user = await opt.ctx.instance.sys.users.getUserByUsername(opt.input.username.toLowerCase()); - - if (user === undefined) { - throw new TRPCError({ - code: "NOT_FOUND", - message: "Failed to find the user", - }); - } - - const session = await opt.ctx.instance.sys.authorization.createPasskeySession( - user.userId, - AuthorizedDeviceType.UnknownBrowser, - opt.input.passkeyResponse, - opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", - ); - - if (session === undefined) { - return { - type: "error", - message: "Failed to create a session?", - }; - } - - opt.ctx.rawRequest.resHeaders.set( - "set-cookie", - Bun.Cookie.from("Authorization", session, { - secure: false, - expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14).toUTCString(), - }).serialize(), - ); - - return { - type: "success", - sessionToken: session, - }; - }), - isAuthenticated: publicProcedure.output(z.object({ authenticated: z.boolean() })).query(async (opt) => { - const cookieString = opt.ctx.rawRequest.req.headers?.get("cookie"); - - if (cookieString === null) { - return { - authenticated: false, - }; - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - const userId = await opt.ctx.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); - - if (userId === undefined) { - return { - authenticated: false, - }; - } - - return { - authenticated: true, - }; - }), - logout: procedure.output(z.object({ success: z.literal(true) })).mutation(async (opt) => { - const cookieString = opt.ctx.rawRequest.req.headers?.get("cookie"); - - if (cookieString === null) { - return { - success: true, - }; - } - - const parsedCookie = Bun.Cookie.parse(cookieString); - - await opt.ctx.instance.sys.authorization.endSessionByToken(decodeURIComponent(parsedCookie.value)); - - return { - success: true, - }; - }), - }, - termsOfUse: publicProcedure.query(async (opt) => { - const date = new Date(opt.ctx.instance.sys.configuration.termsOfUse.lastUpdated); - - const localeDateString: string = date.toLocaleDateString("en-GB", { - day: "numeric", - month: "long", - year: "numeric", - }); - - const getOrdinalSuffix = (day: number): string => { - if (day > 3 && day < 21) return "th"; - switch (day % 10) { - case 1: - return "st"; - case 2: - return "nd"; - case 3: - return "rd"; - default: - return "th"; - } - }; - - const day: number = date.getDate(); - const formattedDate: string = `${day}${getOrdinalSuffix(day)} ${localeDateString.split(" ")[1]}, ${localeDateString.split(" ")[2]}`; - - return `Terms of Use: ${opt.ctx.instance.sys.configuration.displayName} -Effective Date: ${formattedDate} - -${opt.ctx.instance.sys.configuration.termsOfUse.message}`; - }), - app: { - navigation: { - user: { - name: procedure - .output( - z.object({ - username: z.string(), - forename: z.string(), - surname: z.string(), - }), - ) - .query(async (opt) => { - const db = opt.ctx.instance.sys.database.postgres(); - - const user = (await db`SELECT username, forename, surname FROM users WHERE id = ${opt.ctx.userId};`)?.[0]; - - if (!user) { - throw new TRPCError({ - code: "NOT_FOUND", - cause: { message: "User does not exist" }, - }); - } - - return { - username: user.username || "@", - forename: user.forename || "Unknown", - surname: user.surname || "", - }; - }), - }, - getApplications: procedure - .output( - z.array( - z.object({ - location: z.object({ - type: z.union([z.literal("local"), z.literal("remote")]), - value: z.string(), - }), - icon: z.object({ - type: z.union([z.literal("icon"), z.literal("image")]), - value: z.string(), - }), - label: z.string(), - id: z.string(), - }), - ), - ) - .query(async (opt) => { - const applications = opt.ctx.instance.sys.applications.getEnabledApplications(); - - return applications.map((app) => { - let icon = { - type: "icon" as "icon" | "image", - value: "indeterminate_question_box", - }; - - if (app.manifest?.icon) { - if (app.manifest.icon.type === "image") { - icon = { - type: "image", - value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, - }; - } else { - icon = { - type: "icon", - value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, - }; - } - } - - return { - icon: icon, - label: app.manifest?.displayName || "Unknown", - location: { - type: "local", - value: `/app/${app.manifest?.id}` || "/404", - }, - id: app.manifest?.id || "unknown", - }; - }); - }), - getQuickShortcuts: procedure.query(async (opt) => { - const a = opt.ctx.instance.sys.settings.applicationSettings["core"].find((s) => s.id === "quick_shortcuts"); - - if (!a) throw "The core:quick_shortcuts setting is somehow missing???"; - - const quickShortcuts = (await a.onValueChange(opt.ctx.userId)) as string[]; - - const applications = opt.ctx.instance.sys.applications.getEnabledApplications(); - - return quickShortcuts - .map((shortcut) => { - const app = applications.find((a) => a.manifest?.id === shortcut); - - if (!app) return undefined; - - let icon = { - type: "icon" as "icon" | "image", - // TODO: replace this with an image link! - value: "indeterminate_question_box", - }; - - if (app.manifest?.icon) { - if (app.manifest.icon.type === "image") { - icon = { - type: "image", - value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, - }; - } else { - icon = { - type: "icon", - value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, - }; - } - } - - return { - icon: icon, - label: app.manifest?.displayName || "Unknown", - location: { - type: "local", - value: `/app/${app.manifest?.id}` || "/404", - }, - id: app.manifest?.id || "unknown", - }; - }) - .filter((qs) => qs !== undefined); - }), - }, - notifications: { - listener: procedure - // @ts-ignore - .subscription(async function* (opt) { - for await (const [data] of on(opt.ctx.instance.sys.notifications.eventEmitter, WorkspacesNotificationEventEmitterEvent.SendNotification, { - signal: opt.signal, - })) { - const notification = data as WorkspacesNotification; - if (notification.recipient === opt.ctx.userId) { - notifications.push(notification); - - yield notification; - } - } - }), - respond: procedure - .input( - z.object({ - uuid: z.string(), - responseType: z.literal("button"), - value: z.string(), - }), - ) - .output( - z.object({ - ok: z.boolean(), - action: z - .object({ type: z.literal("navigate"), value: z.string() }) - .or(z.object({ type: z.literal("reload") })) - .optional(), - }), - ) - .mutation(async (_) => { - // const notification = notifications.find((n) => n.uuid === opt.input.uuid); - - // if (notification) { - // let output: - // | { - // type: "navigate"; - // value: string; - // } - // | { - // type: "reload"; - // }; - - // if (opt.input.responseType === "button") { - // output = notification.optionsCallbacks?.onButton(opt.input.value); - // } - - // notifications = notifications.filter((n) => n.uuid !== notification.uuid); - - // if (output !== undefined) { - // return { ok: true, action: output.action }; - // } else { - // return { ok: true }; - // } - // } - - return { ok: false }; - }), - }, - }, - theme: { - get: procedure.output(z.any().or(z.literal(false))).query(async (opt) => { - const db = opt.ctx.instance.sys.database.postgres(); - - // biome-ignore lint/suspicious/noExplicitAny: unrequired - const themeValues = (await db`SELECT color_scheme FROM public.users WHERE id = ${opt.ctx.userId}`) as any; - - return themeValues?.[0]?.color_scheme || false; - }), - }, -}); - -export type WorkspacesTRPCRouter = typeof workspacesRouter; +import { getCookies, setCookie } from "@std/http/cookie"; +import { initTRPC, TRPCError } from "@trpc/server"; +import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; +import * as hiBase32 from "hi-base32"; +import * as nodeCrypto from "node:crypto"; +import { on } from "node:events"; +import * as OTPAuth from "otpauth"; +import z from "zod"; +import type { Instance } from "../index.ts"; +import { AuthorizedDeviceType } from "./authorization.ts"; +import { WorkspacesFeatureFlags } from "./configuration.ts"; +import { type WorkspacesNotification, WorkspacesNotificationEventEmitterEvent } from "./notifications.ts"; +import type { WorkspacesUser } from "./users.ts"; + +export const createTRPCContext = (instance: Instance) => (opt: FetchCreateContextFnOptions, server: Deno.HttpServer) => { + return { + rawRequest: { + req: opt.req, + resHeaders: opt.resHeaders, + server: server, + }, + instance: instance, + }; +}; + +export const t = initTRPC.context>().create({ + sse: { + ping: { + // Enable periodic ping messages to keep connection alive + enabled: true, + // Send ping message every 2s + intervalMs: 4000, + }, + client: { + reconnectAfterInactivityMs: 5000, + }, + }, +}); + +export const publicProcedure = t.procedure.use(async (opt) => { + return opt.next({ + ctx: { + userId: "THIS CAN ONLY BE ACCESSED FROM A NON-PUBLIC PROCEDURE", + }, + }); +}); +export const procedure = t.procedure.use(async (opt) => { + const cookies = getCookies(opt.ctx.rawRequest.req.headers); + + if (!cookies.Authorization) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "missing auth cookie", + }); + } + + const userId = await opt.ctx.instance.sys.authorization.verifySession(decodeURIComponent(cookies.Authorization!)); + + if (userId === undefined) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "invalid session" }); + } + + return opt.next({ + ctx: { + userId: userId, + user: (): Promise => + // @ts-ignore + opt.ctx.instance.sys.users.getUserById(userId), + }, + }); +}); +export const adminProcedure = procedure.use(async (opt) => { + const user = await opt.ctx.instance.sys.users.getUserById(opt.ctx.userId); + + if (!user) { + throw new TRPCError({ code: "UNAUTHORIZED", message: "invalid session" }); + } + + if (!(await user.isAdministrator())) { + throw new TRPCError({ + code: "UNAUTHORIZED", + message: "user lacks administrator permissions", + }); + } + + return opt.next(); +}); + +const temporaryTwoFactorSecrets: Map = new Map(); +const emailSignupVerificationCodes: Map = new Map(); +const notifications: WorkspacesNotification[] = []; + +export const workspacesRouter = t.router({ + authorization: { + signupRequirements: publicProcedure + .output( + z.object({ + email: z.boolean(), + twoFactorAuthentication: z.boolean(), + passwordMinimumLength: z.number().optional(), + passwordContains: z + .object({ + minimumUppercase: z.number().optional(), + minimumLowercase: z.number().optional(), + minimumNumbers: z.number().optional(), + minimumSymbols: z.number().optional(), + }) + .optional(), + }), + ) + .query(async (opt) => { + return opt.ctx.instance.sys.configuration.signupRequirements; + }), + checkEmailAddressOwnership: publicProcedure + .input(z.object({ emailAddress: z.string() })) + .output(z.boolean().or(z.string())) + .mutation(async (opt) => { + if (emailSignupVerificationCodes.has(opt.input.emailAddress)) { + return "An email has already been sent, please wait 5 minutes before sending another."; + } + + let emailCode = ""; + const CODE_LENGTH = 8; + const CODE_VALID_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + for (let i = CODE_LENGTH; i > 0; --i) emailCode += CODE_VALID_CHARS[Math.floor(Math.random() * CODE_VALID_CHARS.length)]; + + emailSignupVerificationCodes.set(opt.input.emailAddress, emailCode); + // TODO: send an email... + opt.ctx.instance.log.system.info(`Email code for email '${opt.input.emailAddress}' is '${emailCode}'`); + + return true; + }), + validateEmailCode: publicProcedure.input(z.object({ emailAddress: z.string(), emailCode: z.string() })).query(async (opt) => { + if (emailSignupVerificationCodes.get(opt.input.emailAddress) === opt.input.emailCode) { + return true; + } + + return false; + }), + isUsernameValid: publicProcedure.input(z.string()).query(async (opt) => { + if ((await opt.ctx.instance.sys.users.getUserByUsername(opt.input)) === undefined) return true; + + return false; + }), + canSignup: publicProcedure.query(async (opt) => { + if (opt.ctx.instance.sys.configuration.hasFeature(WorkspacesFeatureFlags.AllowUserSignups)) return true; + + return false; + }), + signup: publicProcedure + .input( + z.union([ + z.object({ + username: z.string(), + password: z.string(), + emailAddress: z.string(), + emailCode: z.string(), + displayName: z.string(), + gender: z.string(), + bio: z.string(), + }), + z.object({ + username: z.string(), + password: z.string(), + displayName: z.string(), + gender: z.string(), + bio: z.string(), + }), + ]), + ) + .output( + z.union([ + z.object({ type: z.literal("error"), message: z.string() }), + z.object({ + type: z.literal("success"), + sessionToken: z.string(), + notice: z.boolean().optional(), + }), + ]), + ) + .mutation(async (opt) => { + if (!opt.ctx.instance.sys.configuration.hasFeature(WorkspacesFeatureFlags.AllowUserSignups)) + return { + type: "error", + message: "This instance has disabled user signups", + }; + + const username = opt.input.username.toLowerCase(); + + if (opt.ctx.instance.sys.configuration.signupRequirements.email) { + if (!("emailAddress" in opt.input)) { + return { + type: "error", + message: "This instance requires an email address for signups!", + }; + } + + if (opt.input.emailCode !== emailSignupVerificationCodes.get(opt.input.emailAddress)) + return { + type: "error", + message: "The email code did not match!", + }; + } + + const uid = await opt.ctx.instance.sys.users.createUser(username, opt.input.password); + + if (uid === undefined) + return { + type: "error", + message: "Failed to create the user", + }; + + const user = await opt.ctx.instance.sys.users.getUserById(uid); + + if (user === undefined) + return { + type: "error", + message: "Failed to fetch the user", + }; + + const splitDisplayName = opt.input.displayName.split(" "); + await user.setFullName(splitDisplayName[0], splitDisplayName.slice(1).join(" ")); + + if ("emailAddress" in opt.input) { + await user.setEmail(opt.input.emailAddress); + } + + await user.setBio(opt.input.bio); + + if (opt.input.gender === "male" || opt.input.gender === "female" || opt.input.gender === "other") await user.setGender(opt.input.gender); + + await user.setQuota(opt.ctx.instance.sys.configuration.userDefault.quotaSize); + + const session = await opt.ctx.instance.sys.authorization.createPasswordSession( + user.userId, + opt.input.password, + AuthorizedDeviceType.UnknownBrowser, + undefined, + opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", + ); + + if (session === undefined) { + return { + type: "error", + message: "Failed to create a session?", + }; + } + + setCookie(opt.ctx.rawRequest.resHeaders, { + name: "Authorization", + value: session, + secure: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14), + }); + + return { + type: "success", + sessionToken: session, + }; + }), + confirmTwoFactor: procedure.input(z.object({ twoFactorCode: z.string() })).mutation(async (opt) => { + const user = await opt.ctx.user(); + const secretString = temporaryTwoFactorSecrets.get(user.userId); + + if (secretString === undefined) { + opt.ctx.instance.log.system.warning( + `(${user.userId})${await user.getUsername()} Tried to confirm a two factor code, but they lacked a temporary secret?`, + ); + + return false; + } + + const totp = new OTPAuth.TOTP({ + issuer: opt.ctx.instance.sys.configuration.proxyUrl[0], + label: `${opt.ctx.instance.sys.configuration.displayName} (Workspace)`, + algorithm: "SHA1", + digits: 6, + secret: secretString, + }); + + if (totp.validate({ token: opt.input.twoFactorCode }) !== null) { + temporaryTwoFactorSecrets.delete(user.userId); + await opt.ctx.instance.sys.authorization.setTwoFactorAuthenticationSecret(user.userId, secretString); + opt.ctx.instance.log.system.success(`(${user.userId})${await user.getUsername()} Setup two-factor authentication on their account!`); + + return true; + } + + return false; + }), + enableTwoFactor: procedure + .output( + z + .object({ + twoFactorSecret: z.string(), + twoFactorSecretURI: z.string(), + }) + .optional(), + ) + .mutation(async (opt) => { + const generateSecretString = () => { + const buffer = nodeCrypto.randomBytes(15); + const base32 = hiBase32.encode(buffer).replace(/=/g, "").substring(0, 24); + return base32; + }; + + const user = await opt.ctx.user(); + + if (await opt.ctx.instance.sys.authorization.hasTwoFactorAuthenticationSecret(user.userId)) { + opt.ctx.instance.log.system.warning( + `User (${user.userId})${await user.getUsername()} has attempted to re-setup their two factor from the signup method... this is suspicious...`, + ); + + return undefined; + } + + let secretString = generateSecretString(); + const prevSecretString = temporaryTwoFactorSecrets.get(user.userId); + + if (prevSecretString) { + secretString = prevSecretString; + } + + const totp = new OTPAuth.TOTP({ + issuer: opt.ctx.instance.sys.configuration.proxyUrl[0], + label: `${opt.ctx.instance.sys.configuration.displayName} (Workspace)`, + algorithm: "SHA1", + digits: 6, + secret: secretString, + }); + + temporaryTwoFactorSecrets.set(user.userId, secretString); + + return { + twoFactorSecretURI: totp.toString(), + twoFactorSecret: secretString, + }; + }), + passwordSignin: publicProcedure + .input( + z.object({ + username: z.string(), + password: z.string(), + twoFactorCode: z.string().optional(), + }), + ) + .output( + z.union([ + z.object({ type: z.literal("error"), message: z.string() }), + z.object({ type: z.literal("success"), sessionToken: z.string() }), + z.object({ type: z.literal("twofactor") }), + ]), + ) + .mutation(async (opt) => { + const username = opt.input.username.toLowerCase(); + const user = await opt.ctx.instance.sys.users.getUserByUsername(username); + + if (user === undefined) + return { + type: "error", + message: "Failed to find the user", + }; + + if (await opt.ctx.instance.sys.authorization.hasTwoFactorAuthenticationSecret(user.userId)) { + if (opt.input.twoFactorCode === undefined) + return { + type: "twofactor", + }; + } + + const session = await opt.ctx.instance.sys.authorization.createPasswordSession( + user.userId, + opt.input.password, + AuthorizedDeviceType.UnknownBrowser, + opt.input.twoFactorCode, + opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", + ); + + if (session === undefined) { + return { + type: "error", + message: "Failed to create a session?", + }; + } + + setCookie(opt.ctx.rawRequest.resHeaders, { + name: "Authorization", + value: session, + secure: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14), + }); + + return { + type: "success", + sessionToken: session, + }; + }), + passkeyRequestSignin: publicProcedure + .input( + z.object({ + username: z.string(), + }), + ) + .query(async (opt) => { + const user = await opt.ctx.instance.sys.users.getUserByUsername(opt.input.username.toLowerCase()); + + if (user === undefined) { + return undefined; + } + + return opt.ctx.instance.sys.authorization.requestPasskeySession(user.userId); + }), + passkeyCompleteSignin: publicProcedure + .input( + z.object({ + username: z.string(), + passkeyResponse: z.any(), + }), + ) + .mutation(async (opt) => { + const user = await opt.ctx.instance.sys.users.getUserByUsername(opt.input.username.toLowerCase()); + + if (user === undefined) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Failed to find the user", + }); + } + + const session = await opt.ctx.instance.sys.authorization.createPasskeySession( + user.userId, + AuthorizedDeviceType.UnknownBrowser, + opt.input.passkeyResponse, + opt.ctx.rawRequest.req.headers.get("X-Real-IP")?.split(":")?.[0] || "missing-caddy-ip", + ); + + if (session === undefined) { + return { + type: "error", + message: "Failed to create a session?", + }; + } + + setCookie(opt.ctx.rawRequest.resHeaders, { + name: "Authorization", + value: session, + secure: true, + expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 14), + }); + + return { + type: "success", + sessionToken: session, + }; + }), + isAuthenticated: publicProcedure.output(z.object({ authenticated: z.boolean() })).query(async (opt) => { + const cookieString = opt.ctx.rawRequest.req.headers?.get("cookie"); + + if (cookieString === null) { + return { + authenticated: false, + }; + } + + const parsedCookie = Bun.Cookie.parse(cookieString); + + const userId = await opt.ctx.instance.sys.authorization.verifySession(decodeURIComponent(parsedCookie.value)); + + if (userId === undefined) { + return { + authenticated: false, + }; + } + + return { + authenticated: true, + }; + }), + logout: procedure.output(z.object({ success: z.literal(true) })).mutation(async (opt) => { + const cookieString = opt.ctx.rawRequest.req.headers?.get("cookie"); + + if (cookieString === null) { + return { + success: true, + }; + } + + const parsedCookie = Bun.Cookie.parse(cookieString); + + await opt.ctx.instance.sys.authorization.endSessionByToken(decodeURIComponent(parsedCookie.value)); + + return { + success: true, + }; + }), + }, + termsOfUse: publicProcedure.query(async (opt) => { + const date = new Date(opt.ctx.instance.sys.configuration.termsOfUse.lastUpdated); + + const localeDateString: string = date.toLocaleDateString("en-GB", { + day: "numeric", + month: "long", + year: "numeric", + }); + + const getOrdinalSuffix = (day: number): string => { + if (day > 3 && day < 21) return "th"; + switch (day % 10) { + case 1: + return "st"; + case 2: + return "nd"; + case 3: + return "rd"; + default: + return "th"; + } + }; + + const day: number = date.getDate(); + const formattedDate: string = `${day}${getOrdinalSuffix(day)} ${localeDateString.split(" ")[1]}, ${localeDateString.split(" ")[2]}`; + + return `Terms of Use: ${opt.ctx.instance.sys.configuration.displayName} +Effective Date: ${formattedDate} + +${opt.ctx.instance.sys.configuration.termsOfUse.message}`; + }), + app: { + navigation: { + user: { + name: procedure + .output( + z.object({ + username: z.string(), + forename: z.string(), + surname: z.string(), + }), + ) + .query(async (opt) => { + const db = opt.ctx.instance.sys.database.postgres(); + + const user = (await db`SELECT username, forename, surname FROM users WHERE id = ${opt.ctx.userId};`)?.[0]; + + if (!user) { + throw new TRPCError({ + code: "NOT_FOUND", + cause: { message: "User does not exist" }, + }); + } + + return { + username: user.username || "@", + forename: user.forename || "Unknown", + surname: user.surname || "", + }; + }), + }, + getApplications: procedure + .output( + z.array( + z.object({ + location: z.object({ + type: z.union([z.literal("local"), z.literal("remote")]), + value: z.string(), + }), + icon: z.object({ + type: z.union([z.literal("icon"), z.literal("image")]), + value: z.string(), + }), + label: z.string(), + id: z.string(), + }), + ), + ) + .query(async (opt) => { + const applications = opt.ctx.instance.sys.applications.getEnabledApplications(); + + return applications.map((app) => { + let icon = { + type: "icon" as "icon" | "image", + value: "indeterminate_question_box", + }; + + if (app.manifest?.icon) { + if (app.manifest.icon.type === "image") { + icon = { + type: "image", + value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, + }; + } else { + icon = { + type: "icon", + value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, + }; + } + } + + return { + icon: icon, + label: app.manifest?.displayName || "Unknown", + location: { + type: "local", + value: `/app/${app.manifest?.id}` || "/404", + }, + id: app.manifest?.id || "unknown", + }; + }); + }), + getQuickShortcuts: procedure.query(async (opt) => { + const a = opt.ctx.instance.sys.settings.applicationSettings["core"].find((s) => s.id === "quick_shortcuts"); + + if (!a) throw "The core:quick_shortcuts setting is somehow missing???"; + + const quickShortcuts = (await a.onValueChange(opt.ctx.userId)) as string[]; + + const applications = opt.ctx.instance.sys.applications.getEnabledApplications(); + + return quickShortcuts + .map((shortcut) => { + const app = applications.find((a) => a.manifest?.id === shortcut); + + if (!app) return undefined; + + let icon = { + type: "icon" as "icon" | "image", + // TODO: replace this with an image link! + value: "indeterminate_question_box", + }; + + if (app.manifest?.icon) { + if (app.manifest.icon.type === "image") { + icon = { + type: "image", + value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, + }; + } else { + icon = { + type: "icon", + value: `${opt.ctx.instance.sys.configuration.proxyUrl}/api/application/${app.manifest.id}/icon/`, + }; + } + } + + return { + icon: icon, + label: app.manifest?.displayName || "Unknown", + location: { + type: "local", + value: `/app/${app.manifest?.id}` || "/404", + }, + id: app.manifest?.id || "unknown", + }; + }) + .filter((qs) => qs !== undefined); + }), + }, + notifications: { + listener: procedure + // @ts-ignore + .subscription(async function* (opt) { + for await (const [data] of on(opt.ctx.instance.sys.notifications.eventEmitter, WorkspacesNotificationEventEmitterEvent.SendNotification, { + signal: opt.signal, + })) { + const notification = data as WorkspacesNotification; + if (notification.recipient === opt.ctx.userId) { + notifications.push(notification); + + yield notification; + } + } + }), + respond: procedure + .input( + z.object({ + uuid: z.string(), + responseType: z.literal("button"), + value: z.string(), + }), + ) + .output( + z.object({ + ok: z.boolean(), + action: z + .object({ type: z.literal("navigate"), value: z.string() }) + .or(z.object({ type: z.literal("reload") })) + .optional(), + }), + ) + .mutation(async (_) => { + // const notification = notifications.find((n) => n.uuid === opt.input.uuid); + + // if (notification) { + // let output: + // | { + // type: "navigate"; + // value: string; + // } + // | { + // type: "reload"; + // }; + + // if (opt.input.responseType === "button") { + // output = notification.optionsCallbacks?.onButton(opt.input.value); + // } + + // notifications = notifications.filter((n) => n.uuid !== notification.uuid); + + // if (output !== undefined) { + // return { ok: true, action: output.action }; + // } else { + // return { ok: true }; + // } + // } + + return { ok: false }; + }), + }, + }, + theme: { + get: procedure.output(z.any().or(z.literal(false))).query(async (opt) => { + const db = opt.ctx.instance.sys.database.postgres(); + + // biome-ignore lint/suspicious/noExplicitAny: unrequired + const themeValues = (await db`SELECT color_scheme FROM public.users WHERE id = ${opt.ctx.userId}`) as any; + + return themeValues?.[0]?.color_scheme || false; + }), + }, +}); + +export type WorkspacesTRPCRouter = typeof workspacesRouter; diff --git a/instance/src/systems/users.ts b/backend/src/systems/users.ts similarity index 100% rename from instance/src/systems/users.ts rename to backend/src/systems/users.ts diff --git a/instance/src/systems/webFrontend.ts b/backend/src/systems/webFrontend.ts similarity index 85% rename from instance/src/systems/webFrontend.ts rename to backend/src/systems/webFrontend.ts index c2aa07b..5146aa0 100644 --- a/instance/src/systems/webFrontend.ts +++ b/backend/src/systems/webFrontend.ts @@ -1,28 +1,25 @@ -import path from "node:path"; -import { createServer as createViteServer, type ViteDevServer } from "vite"; -import type { Instance } from "../index.ts"; -import System from "../system.ts"; - -export default class WebFrontendSystem extends System { - viteServer!: ViteDevServer; - - constructor(instance: Instance) { - super("web_frontend", instance); - } - - override async startup(): Promise { - if (this.instance.sys.configuration.isDevMode) { - this.viteServer = await createViteServer({ - configFile: path.join(this.instance.sys.filesystem.SRC_ROOT, "web/vite.config.ts"), - root: path.join(this.instance.sys.filesystem.SRC_ROOT, "web"), - clearScreen: false, - optimizeDeps: { - exclude: ["fsevents"], - }, - }); - await this.viteServer.listen(); - this.log.info("Listening for web requests:"); - } - return true; - } -} +import path from "node:path"; +import { createServer as createViteServer, type ViteDevServer } from "vite"; +import type { Instance } from "../index.ts"; +import System from "../system.ts"; + +export default class WebFrontendSystem extends System { + viteServer!: ViteDevServer; + + constructor(instance: Instance) { + super("web_frontend", instance); + } + + override async startup(): Promise { + if (this.instance.sys.configuration.isDevMode) { + this.viteServer = await createViteServer({ + configFile: path.join(this.instance.sys.filesystem.SRC_ROOT, "../../web/vite.config.ts"), + root: path.join(this.instance.sys.filesystem.SRC_ROOT, "../../web"), + clearScreen: false, + }); + await this.viteServer.listen(); + this.log.info("Listening for web requests:"); + } + return true; + } +} diff --git a/deno.json b/deno.json index 6cf7105..bb64cf9 100644 --- a/deno.json +++ b/deno.json @@ -1,18 +1,18 @@ { - "workspace": ["instance/", "instance/src/web/", "applications/*/web/", "applications/*/backend/", "applications/*/electron/", "uikit-solid"], + "workspace": ["backend/", "web/", "applications/*/web/", "applications/*/backend/", "applications/*/electron/", "uikit-solid"], "tasks": { - "dev": "deno -A --watch instance/src/index.ts", - "start": "deno -A instance/src/index.ts", - "dev-debug": "deno -A --inspect-brk instance/src/index.ts", - "build-web": "deno --cwd instance/src/web build", - "uikit": "deno --cwd uikit-solid dev", - "preinstall": "git pull --recurse-submodules" + "dev": "deno -A --watch backend/src/index.ts", + "start": "deno -A backend/src/index.ts", + "dev-debug": "deno -A --inspect-brk backend/src/index.ts", + "build-web": "deno --cwd web build", + "uikit": "deno --cwd uikit-solid dev" }, "permissions": { "default": { "all": true } }, + "nodeModulesDir": "auto", "allowScripts": [ "npm:canvas@3.2.3", "npm:core-js@2.6.12", diff --git a/deno.lock b/deno.lock index 691af85..0e9cd38 100644 --- a/deno.lock +++ b/deno.lock @@ -16,6 +16,7 @@ "npm:@material-symbols/svg-700@~0.42.3": "0.42.3", "npm:@material/material-color-utilities@0.3": "0.3.0", "npm:@mediapipe/tasks-vision@~0.10.32": "0.10.35", + "npm:@napi-rs/canvas@1": "1.0.0", "npm:@opentui/core@~0.4.1": "0.4.1_web-tree-sitter@0.25.10_typescript@6.0.3", "npm:@simplewebauthn/browser@^13.3.0": "13.3.0", "npm:@simplewebauthn/server@^13.3.0": "13.3.1", @@ -48,7 +49,6 @@ "npm:ansi-escapes@^7.2.0": "7.3.0", "npm:biome@~0.3.3": "0.3.3", "npm:canvas@^3.2.1": "3.2.3", - "npm:canvas@^3.2.3": "3.2.3", "npm:chalk@^5.5.0": "5.6.2", "npm:clsx@^2.1.1": "2.1.1", "npm:cookies@~0.9.1": "0.9.1", @@ -974,6 +974,77 @@ "@mediapipe/tasks-vision@0.10.35": { "integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==" }, + "@napi-rs/canvas-android-arm64@1.0.0": { + "integrity": "sha512-3hNKJObUK7JsCF9aJlVCs1J0/KE/gGfZNeK8MO1ge6bB3aicr5walGme9t9No1f/oyk9GgvdAT/rjSdsx3gbIw==", + "os": ["android"], + "cpu": ["arm64"] + }, + "@napi-rs/canvas-darwin-arm64@1.0.0": { + "integrity": "sha512-ZIja19/BiGz2puhki+WUYSRriwFeFJ8Mi9eK3hZdSS85w4Y60cuEAJVhMCfKwswQkKkUtrnzdKMBuO7TupvexA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, + "@napi-rs/canvas-darwin-x64@1.0.0": { + "integrity": "sha512-hImggWc82jqZVpEsFR9S7PE9OQYjq/H/D7vwCGB6X1jRH+UVBP1+1niJTPBOat1B154T6GKK7/kcFtoWgjgFzQ==", + "os": ["darwin"], + "cpu": ["x64"] + }, + "@napi-rs/canvas-linux-arm-gnueabihf@1.0.0": { + "integrity": "sha512-hlJRy6d+kWLKVOG/+1rEvNQVURZ0DxxRPJsLmEWwhwiXZUJc0BF5o9esALHSEP4CoJK4wChRtj3hnyBgVx2oWA==", + "os": ["linux"], + "cpu": ["arm"] + }, + "@napi-rs/canvas-linux-arm64-gnu@1.0.0": { + "integrity": "sha512-5Hru4T3RXkosRQafcjelv7AUzw9mXqmGYsxnzeDDOWveFCJyEPMSJltvGCM+jfH98seOCbfwm9KyFg6Jm5FhAA==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@napi-rs/canvas-linux-arm64-musl@1.0.0": { + "integrity": "sha512-LTUl9jS8WsLSUGaxQZKQkxfluOJRpgvBuxxdM4pYcjib+di8AU4OzQc6+L6SzGMLcKc9H0RAjojRatBhTMqYdg==", + "os": ["linux"], + "cpu": ["arm64"] + }, + "@napi-rs/canvas-linux-riscv64-gnu@1.0.0": { + "integrity": "sha512-Iz931SAZf+WVDzpjk52Q3ffW3zw0YflFwEZMgs036Wfu1kX/LrwT9wGjsuSqyduqefUkl91/vTdAjn8hQu5ezA==", + "os": ["linux"], + "cpu": ["riscv64"] + }, + "@napi-rs/canvas-linux-x64-gnu@1.0.0": { + "integrity": "sha512-pFEQ5eFK4JusgN1K6KkO9DKP/Hi1WMJOkF8Ch03/khTc4bFbCKkCCsJG4YcOMOW9bI4XbT2/eMAWxhO0xaWgPA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@napi-rs/canvas-linux-x64-musl@1.0.0": { + "integrity": "sha512-jnvr8NrLHiZ3NCiOKWqDbkI4Ah+QDrqtZ+sddPZBltEb1mQ2coSvCSJYfict+oAwcm0c970oTmVySpjKP/lnaA==", + "os": ["linux"], + "cpu": ["x64"] + }, + "@napi-rs/canvas-win32-arm64-msvc@1.0.0": { + "integrity": "sha512-y2j9/Gfd5joqiqxdP/L1smqjQ+uAx3C4N0EC7bDHrnZEEH8ToM/OC5p3uHvtj4Lq591aHj+ArL01UDLNwT5HgQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, + "@napi-rs/canvas-win32-x64-msvc@1.0.0": { + "integrity": "sha512-qwdhh9N6Gge/hC4pL9S1tQp0iKwhSl/dYjg7+RGp9k26iRGRi5MqqUyKGOXIWli0zOcuy5Y2wIH/jk2ry6i/jA==", + "os": ["win32"], + "cpu": ["x64"] + }, + "@napi-rs/canvas@1.0.0": { + "integrity": "sha512-Jqxcy1XOIqj+lH9sl1GT+il6GR3uQv13vI2mrwubP3uT8Olak2ClDrK2RnxlQKjwv8BRr4b3ug0YR7c6hBX8wg==", + "optionalDependencies": [ + "@napi-rs/canvas-android-arm64", + "@napi-rs/canvas-darwin-arm64", + "@napi-rs/canvas-darwin-x64", + "@napi-rs/canvas-linux-arm-gnueabihf", + "@napi-rs/canvas-linux-arm64-gnu", + "@napi-rs/canvas-linux-arm64-musl", + "@napi-rs/canvas-linux-riscv64-gnu", + "@napi-rs/canvas-linux-x64-gnu", + "@napi-rs/canvas-linux-x64-musl", + "@napi-rs/canvas-win32-arm64-msvc", + "@napi-rs/canvas-win32-x64-msvc" + ] + }, "@napi-rs/wasm-runtime@1.1.5_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dependencies": [ @@ -5100,11 +5171,12 @@ ] } }, - "instance": { + "backend": { "dependencies": [ "jsr:@std/fs@^1.0.24", "jsr:@std/http@^1.1.1", "jsr:@std/path@^1.1.5", + "npm:@napi-rs/canvas@1", "npm:@opentui/core@~0.4.1" ], "packageJson": { @@ -5118,7 +5190,6 @@ "npm:@types/ws@^8.18.1", "npm:@types/yargs@17.0.35", "npm:ansi-escapes@^7.2.0", - "npm:canvas@^3.2.3", "npm:chalk@^5.5.0", "npm:cookies@~0.9.1", "npm:cors@^2.8.5", @@ -5140,7 +5211,27 @@ ] } }, - "instance/src/web": { + "uikit-solid": { + "packageJson": { + "dependencies": [ + "npm:@material-symbols/svg-700@~0.42.3", + "npm:@solid-primitives/media@^2.3.3", + "npm:@solid-primitives/refs@^1.1.3", + "npm:@solid-primitives/upload@~0.1.3", + "npm:clsx@^2.1.1", + "npm:esbuild-plugin-solid@0.6", + "npm:prettier@^3.6.2", + "npm:sass-embedded@^1.90.0", + "npm:solid-js@^1.9.9", + "npm:solid-styled-components@~0.28.5", + "npm:tsup@^8.5.0", + "npm:typescript@^5.9.2", + "npm:vite-plugin-solid@^2.11.8", + "npm:vite@^8.0.1" + ] + } + }, + "web": { "packageJson": { "dependencies": [ "npm:@material-symbols/svg-700@~0.42.3", @@ -5164,26 +5255,6 @@ "npm:vite@^8.0.8" ] } - }, - "uikit-solid": { - "packageJson": { - "dependencies": [ - "npm:@material-symbols/svg-700@~0.42.3", - "npm:@solid-primitives/media@^2.3.3", - "npm:@solid-primitives/refs@^1.1.3", - "npm:@solid-primitives/upload@~0.1.3", - "npm:clsx@^2.1.1", - "npm:esbuild-plugin-solid@0.6", - "npm:prettier@^3.6.2", - "npm:sass-embedded@^1.90.0", - "npm:solid-js@^1.9.9", - "npm:solid-styled-components@~0.28.5", - "npm:tsup@^8.5.0", - "npm:typescript@^5.9.2", - "npm:vite-plugin-solid@^2.11.8", - "npm:vite@^8.0.1" - ] - } } }, "links": { diff --git a/package.json b/package.json index 4229745..b6509f6 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,5 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.14" - }, - "workspaces": [ - "instance/", - "instance/src/web/", - "applications/*/web/", - "applications/*/backend/", - "applications/*/electron/", - "uikit-solid" - ] + } } diff --git a/instance/src/web/.gitignore b/web/.gitignore similarity index 100% rename from instance/src/web/.gitignore rename to web/.gitignore diff --git a/instance/src/web/deno.json b/web/deno.json similarity index 100% rename from instance/src/web/deno.json rename to web/deno.json diff --git a/instance/src/web/index.html b/web/index.html similarity index 100% rename from instance/src/web/index.html rename to web/index.html diff --git a/instance/src/web/package.json b/web/package.json similarity index 100% rename from instance/src/web/package.json rename to web/package.json diff --git a/instance/src/web/public/app.webmanifest b/web/public/app.webmanifest similarity index 100% rename from instance/src/web/public/app.webmanifest rename to web/public/app.webmanifest diff --git a/instance/src/web/public/assets/generic_background.png b/web/public/assets/generic_background.png similarity index 100% rename from instance/src/web/public/assets/generic_background.png rename to web/public/assets/generic_background.png diff --git a/instance/src/web/public/assets/generic_background.svg b/web/public/assets/generic_background.svg similarity index 100% rename from instance/src/web/public/assets/generic_background.svg rename to web/public/assets/generic_background.svg diff --git a/instance/src/web/public/assets/onlineworkspace/online_workspace_logo.svg b/web/public/assets/onlineworkspace/online_workspace_logo.svg similarity index 100% rename from instance/src/web/public/assets/onlineworkspace/online_workspace_logo.svg rename to web/public/assets/onlineworkspace/online_workspace_logo.svg diff --git a/instance/src/web/public/assets/onlineworkspace/online_workspace_wordmark.svg b/web/public/assets/onlineworkspace/online_workspace_wordmark.svg similarity index 100% rename from instance/src/web/public/assets/onlineworkspace/online_workspace_wordmark.svg rename to web/public/assets/onlineworkspace/online_workspace_wordmark.svg diff --git a/instance/src/web/public/assets/placeholder/avatar.png b/web/public/assets/placeholder/avatar.png similarity index 100% rename from instance/src/web/public/assets/placeholder/avatar.png rename to web/public/assets/placeholder/avatar.png diff --git a/instance/src/web/public/assets/placeholder/placeholder_image.png b/web/public/assets/placeholder/placeholder_image.png similarity index 100% rename from instance/src/web/public/assets/placeholder/placeholder_image.png rename to web/public/assets/placeholder/placeholder_image.png diff --git a/instance/src/web/public/assets/placeholder/placeholder_image.svg b/web/public/assets/placeholder/placeholder_image.svg similarity index 100% rename from instance/src/web/public/assets/placeholder/placeholder_image.svg rename to web/public/assets/placeholder/placeholder_image.svg diff --git a/instance/src/web/public/fonts/roboto.css b/web/public/fonts/roboto.css similarity index 100% rename from instance/src/web/public/fonts/roboto.css rename to web/public/fonts/roboto.css diff --git a/instance/src/web/public/fonts/roboto_flex.css b/web/public/fonts/roboto_flex.css similarity index 100% rename from instance/src/web/public/fonts/roboto_flex.css rename to web/public/fonts/roboto_flex.css diff --git a/instance/src/web/public/fonts/roboto_flex_latin.woff2 b/web/public/fonts/roboto_flex_latin.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_flex_latin.woff2 rename to web/public/fonts/roboto_flex_latin.woff2 diff --git a/instance/src/web/public/fonts/roboto_flex_latin_ext.woff2 b/web/public/fonts/roboto_flex_latin_ext.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_flex_latin_ext.woff2 rename to web/public/fonts/roboto_flex_latin_ext.woff2 diff --git a/instance/src/web/public/fonts/roboto_italic_latin.woff2 b/web/public/fonts/roboto_italic_latin.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_italic_latin.woff2 rename to web/public/fonts/roboto_italic_latin.woff2 diff --git a/instance/src/web/public/fonts/roboto_italic_latin_ext.woff2 b/web/public/fonts/roboto_italic_latin_ext.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_italic_latin_ext.woff2 rename to web/public/fonts/roboto_italic_latin_ext.woff2 diff --git a/instance/src/web/public/fonts/roboto_italic_math.woff2 b/web/public/fonts/roboto_italic_math.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_italic_math.woff2 rename to web/public/fonts/roboto_italic_math.woff2 diff --git a/instance/src/web/public/fonts/roboto_italic_symbols.woff2 b/web/public/fonts/roboto_italic_symbols.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_italic_symbols.woff2 rename to web/public/fonts/roboto_italic_symbols.woff2 diff --git a/instance/src/web/public/fonts/roboto_latin.woff2 b/web/public/fonts/roboto_latin.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_latin.woff2 rename to web/public/fonts/roboto_latin.woff2 diff --git a/instance/src/web/public/fonts/roboto_latin_ext.woff2 b/web/public/fonts/roboto_latin_ext.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_latin_ext.woff2 rename to web/public/fonts/roboto_latin_ext.woff2 diff --git a/instance/src/web/public/fonts/roboto_math.woff2 b/web/public/fonts/roboto_math.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_math.woff2 rename to web/public/fonts/roboto_math.woff2 diff --git a/instance/src/web/public/fonts/roboto_symbols.woff2 b/web/public/fonts/roboto_symbols.woff2 similarity index 100% rename from instance/src/web/public/fonts/roboto_symbols.woff2 rename to web/public/fonts/roboto_symbols.woff2 diff --git a/instance/src/web/src/App.module.scss b/web/src/App.module.scss similarity index 100% rename from instance/src/web/src/App.module.scss rename to web/src/App.module.scss diff --git a/instance/src/web/src/App.tsx b/web/src/App.tsx similarity index 100% rename from instance/src/web/src/App.tsx rename to web/src/App.tsx diff --git a/instance/src/web/src/MissingApp.module.scss b/web/src/MissingApp.module.scss similarity index 100% rename from instance/src/web/src/MissingApp.module.scss rename to web/src/MissingApp.module.scss diff --git a/instance/src/web/src/MissingApp.tsx b/web/src/MissingApp.tsx similarity index 100% rename from instance/src/web/src/MissingApp.tsx rename to web/src/MissingApp.tsx diff --git a/instance/src/web/src/index.scss b/web/src/index.scss similarity index 100% rename from instance/src/web/src/index.scss rename to web/src/index.scss diff --git a/instance/src/web/src/index.tsx b/web/src/index.tsx similarity index 100% rename from instance/src/web/src/index.tsx rename to web/src/index.tsx diff --git a/instance/src/web/src/lib/backend.ts b/web/src/lib/backend.ts similarity index 100% rename from instance/src/web/src/lib/backend.ts rename to web/src/lib/backend.ts diff --git a/instance/src/web/src/lib/trpc.ts b/web/src/lib/trpc.ts similarity index 100% rename from instance/src/web/src/lib/trpc.ts rename to web/src/lib/trpc.ts diff --git a/instance/src/web/src/pages/app/AuthCheck.module.scss b/web/src/pages/app/AuthCheck.module.scss similarity index 100% rename from instance/src/web/src/pages/app/AuthCheck.module.scss rename to web/src/pages/app/AuthCheck.module.scss diff --git a/instance/src/web/src/pages/app/AuthCheck.tsx b/web/src/pages/app/AuthCheck.tsx similarity index 100% rename from instance/src/web/src/pages/app/AuthCheck.tsx rename to web/src/pages/app/AuthCheck.tsx diff --git a/instance/src/web/src/pages/app/Index.tsx b/web/src/pages/app/Index.tsx similarity index 100% rename from instance/src/web/src/pages/app/Index.tsx rename to web/src/pages/app/Index.tsx diff --git a/instance/src/web/src/pages/app/Layout.module.scss b/web/src/pages/app/Layout.module.scss similarity index 100% rename from instance/src/web/src/pages/app/Layout.module.scss rename to web/src/pages/app/Layout.module.scss diff --git a/instance/src/web/src/pages/app/Layout.tsx b/web/src/pages/app/Layout.tsx similarity index 100% rename from instance/src/web/src/pages/app/Layout.tsx rename to web/src/pages/app/Layout.tsx diff --git a/instance/src/web/src/pages/app/Navigation.module.scss b/web/src/pages/app/Navigation.module.scss similarity index 100% rename from instance/src/web/src/pages/app/Navigation.module.scss rename to web/src/pages/app/Navigation.module.scss diff --git a/instance/src/web/src/pages/app/Navigation.tsx b/web/src/pages/app/Navigation.tsx similarity index 100% rename from instance/src/web/src/pages/app/Navigation.tsx rename to web/src/pages/app/Navigation.tsx diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.module.scss b/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.module.scss similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.module.scss rename to web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.module.scss diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.tsx b/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.tsx similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.tsx rename to web/src/pages/app/components/NavigationRail/components/navigationRailApplications/NavigationRailApplications.tsx diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.module.scss b/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.module.scss similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.module.scss rename to web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.module.scss diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.tsx b/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.tsx similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.tsx rename to web/src/pages/app/components/NavigationRail/components/navigationRailAvatar/NavigationRailAvatar.tsx diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.module.scss b/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.module.scss similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.module.scss rename to web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.module.scss diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.tsx b/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.tsx similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.tsx rename to web/src/pages/app/components/NavigationRail/components/navigationRailClock/NavigationRailClock.tsx diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.module.scss b/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.module.scss similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.module.scss rename to web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.module.scss diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.tsx b/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.tsx similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.tsx rename to web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/NavigationRailNotifications.tsx diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.module.scss b/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.module.scss similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.module.scss rename to web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.module.scss diff --git a/instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.tsx b/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.tsx similarity index 100% rename from instance/src/web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.tsx rename to web/src/pages/app/components/NavigationRail/components/navigationRailNotifications/notification/Notification.tsx diff --git a/instance/src/web/src/pages/notFound/Index.tsx b/web/src/pages/notFound/Index.tsx similarity index 100% rename from instance/src/web/src/pages/notFound/Index.tsx rename to web/src/pages/notFound/Index.tsx diff --git a/instance/src/web/src/pages/userSelect/Layout.module.scss b/web/src/pages/userSelect/Layout.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/Layout.module.scss rename to web/src/pages/userSelect/Layout.module.scss diff --git a/instance/src/web/src/pages/userSelect/Layout.tsx b/web/src/pages/userSelect/Layout.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/Layout.tsx rename to web/src/pages/userSelect/Layout.tsx diff --git a/instance/src/web/src/pages/userSelect/forgotPassword/ForgotPassword.module.scss b/web/src/pages/userSelect/forgotPassword/ForgotPassword.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/forgotPassword/ForgotPassword.module.scss rename to web/src/pages/userSelect/forgotPassword/ForgotPassword.module.scss diff --git a/instance/src/web/src/pages/userSelect/forgotPassword/ForgotPassword.tsx b/web/src/pages/userSelect/forgotPassword/ForgotPassword.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/forgotPassword/ForgotPassword.tsx rename to web/src/pages/userSelect/forgotPassword/ForgotPassword.tsx diff --git a/instance/src/web/src/pages/userSelect/login/Login.module.scss b/web/src/pages/userSelect/login/Login.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/login/Login.module.scss rename to web/src/pages/userSelect/login/Login.module.scss diff --git a/instance/src/web/src/pages/userSelect/login/Login.tsx b/web/src/pages/userSelect/login/Login.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/login/Login.tsx rename to web/src/pages/userSelect/login/Login.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/Signup.module.scss b/web/src/pages/userSelect/signup/Signup.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/Signup.module.scss rename to web/src/pages/userSelect/signup/Signup.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/Signup.tsx b/web/src/pages/userSelect/signup/Signup.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/Signup.tsx rename to web/src/pages/userSelect/signup/Signup.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Email/Email.module.scss b/web/src/pages/userSelect/signup/stages/Email/Email.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Email/Email.module.scss rename to web/src/pages/userSelect/signup/stages/Email/Email.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Email/Email.tsx b/web/src/pages/userSelect/signup/stages/Email/Email.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Email/Email.tsx rename to web/src/pages/userSelect/signup/stages/Email/Email.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Guide/Guide.tsx b/web/src/pages/userSelect/signup/stages/Guide/Guide.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Guide/Guide.tsx rename to web/src/pages/userSelect/signup/stages/Guide/Guide.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/GuidePrompt/GuidePrompt.tsx b/web/src/pages/userSelect/signup/stages/GuidePrompt/GuidePrompt.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/GuidePrompt/GuidePrompt.tsx rename to web/src/pages/userSelect/signup/stages/GuidePrompt/GuidePrompt.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Password/Password.module.scss b/web/src/pages/userSelect/signup/stages/Password/Password.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Password/Password.module.scss rename to web/src/pages/userSelect/signup/stages/Password/Password.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Password/Password.tsx b/web/src/pages/userSelect/signup/stages/Password/Password.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Password/Password.tsx rename to web/src/pages/userSelect/signup/stages/Password/Password.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Password/components/Requirement/Requirement.tsx b/web/src/pages/userSelect/signup/stages/Password/components/Requirement/Requirement.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Password/components/Requirement/Requirement.tsx rename to web/src/pages/userSelect/signup/stages/Password/components/Requirement/Requirement.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Profile/Profile.module.scss b/web/src/pages/userSelect/signup/stages/Profile/Profile.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Profile/Profile.module.scss rename to web/src/pages/userSelect/signup/stages/Profile/Profile.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Profile/Profile.tsx b/web/src/pages/userSelect/signup/stages/Profile/Profile.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Profile/Profile.tsx rename to web/src/pages/userSelect/signup/stages/Profile/Profile.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.module.scss b/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.module.scss rename to web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.tsx b/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.tsx rename to web/src/pages/userSelect/signup/stages/TermsOfUse/TermsOfUse.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.module.scss b/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.module.scss rename to web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.tsx b/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.tsx rename to web/src/pages/userSelect/signup/stages/TwoFactorAuthentication/TwoFactorAuthentication.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Username/Username.module.scss b/web/src/pages/userSelect/signup/stages/Username/Username.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Username/Username.module.scss rename to web/src/pages/userSelect/signup/stages/Username/Username.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/Username/Username.tsx b/web/src/pages/userSelect/signup/stages/Username/Username.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/Username/Username.tsx rename to web/src/pages/userSelect/signup/stages/Username/Username.tsx diff --git a/instance/src/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.module.scss b/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.module.scss similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.module.scss rename to web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.module.scss diff --git a/instance/src/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.tsx b/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.tsx similarity index 100% rename from instance/src/web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.tsx rename to web/src/pages/userSelect/signup/stages/VerifyEmail/VerifyEmail.tsx diff --git a/instance/src/web/src/vite-env.d.ts b/web/src/vite-env.d.ts similarity index 100% rename from instance/src/web/src/vite-env.d.ts rename to web/src/vite-env.d.ts diff --git a/instance/src/web/tsconfig.app.json b/web/tsconfig.app.json similarity index 83% rename from instance/src/web/tsconfig.app.json rename to web/tsconfig.app.json index 86eb47e..fd5d4de 100644 --- a/instance/src/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -1,28 +1,35 @@ -{ - "compilerOptions": { - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", - "target": "ES2022", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "preserve", - "jsxImportSource": "solid-js", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "erasableSyntaxOnly": false, - "noFallthroughCasesInSwitch": true, - "noUncheckedSideEffectImports": true - }, - "include": ["src"] -} +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "types": [ + "React" + ], + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": false, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/instance/src/web/tsconfig.json b/web/tsconfig.json similarity index 100% rename from instance/src/web/tsconfig.json rename to web/tsconfig.json diff --git a/instance/src/web/tsconfig.node.json b/web/tsconfig.node.json similarity index 100% rename from instance/src/web/tsconfig.node.json rename to web/tsconfig.node.json diff --git a/instance/src/web/vite.config.ts b/web/vite.config.ts similarity index 71% rename from instance/src/web/vite.config.ts rename to web/vite.config.ts index 92220c5..b67a600 100644 --- a/instance/src/web/vite.config.ts +++ b/web/vite.config.ts @@ -1,32 +1,35 @@ -// import devtools from "solid-devtools/vite"; -import { defineConfig } from "vite"; -import { compression } from "vite-plugin-compression2"; -import solid from "vite-plugin-solid"; - -export default defineConfig({ - plugins: [ - // @ts-ignore - // devtools({ - // /* features options - all disabled by default */ - // autoname: true, // e.g. enable autoname - // }), - solid(), - compression(), - ], - server: { - host: true, - allowedHosts: [process.env.ALLOW_HOST || "localhost"], - hmr: { - clientPort: 443, - protocol: "wss", - host: process.env.ALLOW_HOST || "localhost", - }, - }, - resolve: { - alias: { - "@solidjs/router": "/node_modules/@solidjs/router", - "@ewsgit/uikit-solid": "/node_modules/@ewsgit/uikit-solid", - "@onlineworkspace/workspaces-applications": "/../../../fs/system/vite/Applications.tsx", - }, - }, -}); +// import devtools from "solid-devtools/vite"; +import { defineConfig } from "vite"; +import { compression } from "vite-plugin-compression2"; +import solid from "vite-plugin-solid"; + +export default defineConfig({ + plugins: [ + // @ts-ignore + // devtools({ + // /* features options - all disabled by default */ + // autoname: true, // e.g. enable autoname + // }), + solid(), + compression(), + ], + server: { + host: true, + allowedHosts: [process.env.ALLOW_HOST || "localhost"], + hmr: { + clientPort: 443, + protocol: "wss", + host: process.env.ALLOW_HOST || "localhost", + }, + }, + optimizeDeps: { + exclude: ["fs-events"], + }, + resolve: { + alias: { + "@solidjs/router": "/../node_modules/@solidjs/router", + "@ewsgit/uikit-solid": "/../node_modules/@ewsgit/uikit-solid", + "@onlineworkspace/workspaces-applications": "/../fs/system/vite/Applications.tsx", + }, + }, +}); -- 2.51.2