From 4ea6549ac74ef20e206fadfb39374c436ff4fa0b Mon Sep 17 00:00:00 2001 From: DragonDuck24 Date: Tue, 3 Feb 2026 00:23:43 -0600 Subject: [PATCH] Lots of dev --- .gitignore | 2 + eslint.config.js | 3 +- package-lock.json | 640 +++++++++++++++++- package.json | 6 + src/app.d.ts | 4 +- src/hooks.server.ts | 25 + src/lib/auth/index.server.ts | 12 +- .../custom/image-upload/image-upload.svelte | 166 +++++ src/lib/components/custom/item-listing.svelte | 45 ++ .../{ => custom}/mdev-triangle.svelte | 0 src/lib/components/ui/badge/badge.svelte | 50 ++ src/lib/components/ui/badge/index.ts | 2 + .../components/ui/checkbox/checkbox.svelte | 36 + src/lib/components/ui/checkbox/index.ts | 6 + .../components/ui/dialog/dialog-close.svelte | 7 + .../ui/dialog/dialog-content.svelte | 45 ++ .../ui/dialog/dialog-description.svelte | 17 + .../components/ui/dialog/dialog-footer.svelte | 20 + .../components/ui/dialog/dialog-header.svelte | 20 + .../ui/dialog/dialog-overlay.svelte | 20 + .../components/ui/dialog/dialog-portal.svelte | 7 + .../components/ui/dialog/dialog-title.svelte | 17 + .../ui/dialog/dialog-trigger.svelte | 7 + src/lib/components/ui/dialog/dialog.svelte | 7 + src/lib/components/ui/dialog/index.ts | 34 + src/lib/components/ui/radio-group/index.ts | 10 + .../ui/radio-group/radio-group-item.svelte | 31 + .../ui/radio-group/radio-group.svelte | 19 + src/lib/components/ui/select/index.ts | 37 + .../ui/select/select-content.svelte | 45 ++ .../ui/select/select-group-heading.svelte | 21 + .../components/ui/select/select-group.svelte | 7 + .../components/ui/select/select-item.svelte | 38 ++ .../components/ui/select/select-label.svelte | 20 + .../components/ui/select/select-portal.svelte | 7 + .../select/select-scroll-down-button.svelte | 20 + .../ui/select/select-scroll-up-button.svelte | 20 + .../ui/select/select-separator.svelte | 18 + .../ui/select/select-trigger.svelte | 29 + src/lib/components/ui/select/select.svelte | 11 + src/lib/components/ui/textarea/index.ts | 7 + .../components/ui/textarea/textarea.svelte | 23 + src/lib/consts.ts | 19 + src/lib/db/db.server.ts | 3 +- src/lib/db/users.server.ts | 67 -- src/lib/email/sender.server.ts | 23 + src/lib/shared.ts | 15 +- src/lib/types.ts | 22 - src/lib/types/inquiries.server.ts | 13 + src/lib/types/item.d.ts | 13 + src/lib/types/user.ts | 30 + src/routes/+error.svelte | 34 + src/routes/+layout.server.ts | 9 +- src/routes/+layout.svelte | 74 +- src/routes/+page.server.ts | 89 +++ src/routes/+page.svelte | 109 ++- src/routes/create/+page.server.ts | 0 src/routes/create/+page.svelte | 143 ++++ src/routes/gen-desc.remote.ts | 7 + src/routes/items/[itemid]/+page.svelte | 0 src/routes/login/+page.svelte | 23 +- .../login}/login-form.svelte | 23 +- src/routes/signup/+page.svelte | 4 +- .../signup}/signup-form.svelte | 6 +- static/no-image-available.png | Bin 0 -> 43003 bytes svelte.config.js | 10 +- 66 files changed, 2125 insertions(+), 172 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/components/custom/image-upload/image-upload.svelte create mode 100644 src/lib/components/custom/item-listing.svelte rename src/lib/components/{ => custom}/mdev-triangle.svelte (100%) create mode 100644 src/lib/components/ui/badge/badge.svelte create mode 100644 src/lib/components/ui/badge/index.ts create mode 100644 src/lib/components/ui/checkbox/checkbox.svelte create mode 100644 src/lib/components/ui/checkbox/index.ts create mode 100644 src/lib/components/ui/dialog/dialog-close.svelte create mode 100644 src/lib/components/ui/dialog/dialog-content.svelte create mode 100644 src/lib/components/ui/dialog/dialog-description.svelte create mode 100644 src/lib/components/ui/dialog/dialog-footer.svelte create mode 100644 src/lib/components/ui/dialog/dialog-header.svelte create mode 100644 src/lib/components/ui/dialog/dialog-overlay.svelte create mode 100644 src/lib/components/ui/dialog/dialog-portal.svelte create mode 100644 src/lib/components/ui/dialog/dialog-title.svelte create mode 100644 src/lib/components/ui/dialog/dialog-trigger.svelte create mode 100644 src/lib/components/ui/dialog/dialog.svelte create mode 100644 src/lib/components/ui/dialog/index.ts create mode 100644 src/lib/components/ui/radio-group/index.ts create mode 100644 src/lib/components/ui/radio-group/radio-group-item.svelte create mode 100644 src/lib/components/ui/radio-group/radio-group.svelte create mode 100644 src/lib/components/ui/select/index.ts create mode 100644 src/lib/components/ui/select/select-content.svelte create mode 100644 src/lib/components/ui/select/select-group-heading.svelte create mode 100644 src/lib/components/ui/select/select-group.svelte create mode 100644 src/lib/components/ui/select/select-item.svelte create mode 100644 src/lib/components/ui/select/select-label.svelte create mode 100644 src/lib/components/ui/select/select-portal.svelte create mode 100644 src/lib/components/ui/select/select-scroll-down-button.svelte create mode 100644 src/lib/components/ui/select/select-scroll-up-button.svelte create mode 100644 src/lib/components/ui/select/select-separator.svelte create mode 100644 src/lib/components/ui/select/select-trigger.svelte create mode 100644 src/lib/components/ui/select/select.svelte create mode 100644 src/lib/components/ui/textarea/index.ts create mode 100644 src/lib/components/ui/textarea/textarea.svelte create mode 100644 src/lib/email/sender.server.ts delete mode 100644 src/lib/types.ts create mode 100644 src/lib/types/inquiries.server.ts create mode 100644 src/lib/types/item.d.ts create mode 100644 src/lib/types/user.ts create mode 100644 src/routes/+error.svelte create mode 100644 src/routes/+page.server.ts create mode 100644 src/routes/create/+page.server.ts create mode 100644 src/routes/create/+page.svelte create mode 100644 src/routes/gen-desc.remote.ts create mode 100644 src/routes/items/[itemid]/+page.svelte rename src/{lib/components => routes/login}/login-form.svelte (68%) rename src/{lib/components => routes/signup}/signup-form.svelte (90%) create mode 100644 static/no-image-available.png diff --git a/.gitignore b/.gitignore index cdf9501..40af263 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ node_modules +uploads + # Output .output .vercel diff --git a/eslint.config.js b/eslint.config.js index 2c49fa6..ca4d9f4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,7 +24,8 @@ export default defineConfig( rules: { // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors - 'no-undef': 'off' + 'no-undef': 'off', + 'svelte/no-navigation-without-resolve': 'off' } }, { diff --git a/package-lock.json b/package-lock.json index 21fd447..7f61cdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,10 @@ "dotenv": "^17.2.3", "express": "^5.2.1", "jsonwebtoken": "^9.0.3", + "mode-watcher": "^1.1.0", + "nodemailer": "^7.0.13", "postgres": "^3.4.7", + "sharp": "^0.34.5", "svelte-preprocess": "^6.0.3" }, "devDependencies": { @@ -26,7 +29,10 @@ "@sveltejs/vite-plugin-svelte": "^6.2.0", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/vite": "^4.1.13", + "@types/bcrypt": "^6.0.0", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^22", + "@types/nodemailer": "^7.0.9", "@vitest/browser": "^3.2.4", "bits-ui": "^2.15.4", "clsx": "^2.1.1", @@ -86,6 +92,16 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", @@ -786,6 +802,471 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@internationalized/date": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.1.tgz", @@ -1768,6 +2249,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/chai": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", @@ -1805,6 +2296,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.18.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", @@ -1815,6 +2324,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/nodemailer": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2768,7 +3287,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -3660,7 +4178,6 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", - "dev": true, "license": "MIT" }, "node_modules/ipaddr.js": { @@ -4370,6 +4887,69 @@ "node": ">= 18" } }, + "node_modules/mode-watcher": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-1.1.0.tgz", + "integrity": "sha512-mUT9RRGPDYenk59qJauN1rhsIMKBmWA3xMF+uRwE8MW/tjhaDSCCARqkSuDTq8vr4/2KcAxIGVjACxTjdk5C3g==", + "license": "MIT", + "dependencies": { + "runed": "^0.25.0", + "svelte-toolbelt": "^0.7.1" + }, + "peerDependencies": { + "svelte": "^5.27.0" + } + }, + "node_modules/mode-watcher/node_modules/runed": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.25.0.tgz", + "integrity": "sha512-7+ma4AG9FT2sWQEA0Egf6mb7PBT2vHyuHail1ie8ropfSjvZGtEAx8YTmUjv/APCsdRRxEVvArNjALk9zFSOrg==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz", + "integrity": "sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.23.2", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/mode-watcher/node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.4.tgz", + "integrity": "sha512-9q8oUiBYeXIDLWNK5DfCWlkL0EW3oGbk845VdKlPeia28l751VpfesaB/+7pI6rnbx1I6rqoZ2fZxptOJLxILA==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -4451,6 +5031,15 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/nodemailer": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -5326,6 +5915,50 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5513,7 +6146,6 @@ "version": "1.0.14", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", - "dev": true, "license": "MIT", "dependencies": { "inline-style-parser": "0.2.7" @@ -5903,7 +6535,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, + "devOptional": true, "license": "0BSD" }, "node_modules/tw-animate-css": { diff --git a/package.json b/package.json index 8701458..8a4b481 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,10 @@ "@sveltejs/vite-plugin-svelte": "^6.2.0", "@tailwindcss/forms": "^0.5.10", "@tailwindcss/vite": "^4.1.13", + "@types/bcrypt": "^6.0.0", + "@types/jsonwebtoken": "^9.0.10", "@types/node": "^22", + "@types/nodemailer": "^7.0.9", "@vitest/browser": "^3.2.4", "bits-ui": "^2.15.4", "clsx": "^2.1.1", @@ -55,7 +58,10 @@ "dotenv": "^17.2.3", "express": "^5.2.1", "jsonwebtoken": "^9.0.3", + "mode-watcher": "^1.1.0", + "nodemailer": "^7.0.13", "postgres": "^3.4.7", + "sharp": "^0.34.5", "svelte-preprocess": "^6.0.3" } } diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..d890986 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -3,7 +3,9 @@ declare global { namespace App { // interface Error {} - // interface Locals {} + interface Locals { + user: UserPayload | null; + } // interface PageData {} // interface PageState {} // interface Platform {} diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..d232a6d --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,25 @@ +import jwt from 'jsonwebtoken'; +import * as dotenv from 'dotenv'; + +dotenv.config({ path: '.env' }); + +export const handle = async ({ event, resolve }) => { + const JWT = event.cookies.get('jwt'); + + if (process.env.JWT_SECRET === undefined) { + throw new Error('JWT_SECRET not defined'); + } + + if (!JWT) { + event.locals.user = null; + } else { + try { + event.locals.user = jwt.verify(JWT, process.env.JWT_SECRET); + } catch { + event.cookies.delete('jwt', { path: '/' }); + event.locals.user = null; + } + } + + return await resolve(event); +}; diff --git a/src/lib/auth/index.server.ts b/src/lib/auth/index.server.ts index 7ae5278..0c7c122 100644 --- a/src/lib/auth/index.server.ts +++ b/src/lib/auth/index.server.ts @@ -1,4 +1,4 @@ -import type { User } from '$lib/types'; +import type { User } from '$lib/types/user'; import bcrypt from 'bcrypt'; import sql from '$lib/db/db.server'; import type { Cookies } from '@sveltejs/kit'; @@ -49,22 +49,22 @@ export function setJWT(cookies: Cookies, user: User) { export async function login(email: string, password: string): Promise { try { - const [user] = await sql` - SELECT id, email, password_hash, perms, name + const [user]: User[] = await sql` + SELECT * FROM users WHERE email = ${email}; `; - if (await bcrypt.compare(password, user.password_hash)) { + if (await bcrypt.compare(password, user.password_hash!)) { delete user.password_hash; // eslint-disable-next-line @typescript-eslint/no-unused-expressions sql` UPDATE users - SET last_signin = NOW() + SET last_sign_in = NOW() WHERE id = ${user.id}; `; - return user; + return user; } } catch { throw Error('Error signing in '); diff --git a/src/lib/components/custom/image-upload/image-upload.svelte b/src/lib/components/custom/image-upload/image-upload.svelte new file mode 100644 index 0000000..0842dbb --- /dev/null +++ b/src/lib/components/custom/image-upload/image-upload.svelte @@ -0,0 +1,166 @@ + + + +
+ + + + + {#if previewUrl} + + {/if} +
+ + diff --git a/src/lib/components/custom/item-listing.svelte b/src/lib/components/custom/item-listing.svelte new file mode 100644 index 0000000..3f6f459 --- /dev/null +++ b/src/lib/components/custom/item-listing.svelte @@ -0,0 +1,45 @@ + + + + +
+
+ + + + {#if item.transferred} + In Lost & Found + {:else} + With Finder + {/if} +
{item.description}
+ {#if item.foundLocation} +
+ +
{item.foundLocation}
+
+ {/if} + +
+ +
+
+ + diff --git a/src/lib/components/mdev-triangle.svelte b/src/lib/components/custom/mdev-triangle.svelte similarity index 100% rename from src/lib/components/mdev-triangle.svelte rename to src/lib/components/custom/mdev-triangle.svelte diff --git a/src/lib/components/ui/badge/badge.svelte b/src/lib/components/ui/badge/badge.svelte new file mode 100644 index 0000000..e3164ba --- /dev/null +++ b/src/lib/components/ui/badge/badge.svelte @@ -0,0 +1,50 @@ + + + + + + {@render children?.()} + diff --git a/src/lib/components/ui/badge/index.ts b/src/lib/components/ui/badge/index.ts new file mode 100644 index 0000000..64e0aa9 --- /dev/null +++ b/src/lib/components/ui/badge/index.ts @@ -0,0 +1,2 @@ +export { default as Badge } from "./badge.svelte"; +export { badgeVariants, type BadgeVariant } from "./badge.svelte"; diff --git a/src/lib/components/ui/checkbox/checkbox.svelte b/src/lib/components/ui/checkbox/checkbox.svelte new file mode 100644 index 0000000..0a2b010 --- /dev/null +++ b/src/lib/components/ui/checkbox/checkbox.svelte @@ -0,0 +1,36 @@ + + + + {#snippet children({ checked, indeterminate })} +
+ {#if checked} + + {:else if indeterminate} + + {/if} +
+ {/snippet} +
diff --git a/src/lib/components/ui/checkbox/index.ts b/src/lib/components/ui/checkbox/index.ts new file mode 100644 index 0000000..6d92d94 --- /dev/null +++ b/src/lib/components/ui/checkbox/index.ts @@ -0,0 +1,6 @@ +import Root from "./checkbox.svelte"; +export { + Root, + // + Root as Checkbox, +}; diff --git a/src/lib/components/ui/dialog/dialog-close.svelte b/src/lib/components/ui/dialog/dialog-close.svelte new file mode 100644 index 0000000..840b2f6 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-close.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-content.svelte b/src/lib/components/ui/dialog/dialog-content.svelte new file mode 100644 index 0000000..6e859e5 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-content.svelte @@ -0,0 +1,45 @@ + + + + + + {@render children?.()} + {#if showCloseButton} + + + Close + + {/if} + + diff --git a/src/lib/components/ui/dialog/dialog-description.svelte b/src/lib/components/ui/dialog/dialog-description.svelte new file mode 100644 index 0000000..3845023 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-description.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-footer.svelte b/src/lib/components/ui/dialog/dialog-footer.svelte new file mode 100644 index 0000000..e7ff446 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/dialog/dialog-header.svelte b/src/lib/components/ui/dialog/dialog-header.svelte new file mode 100644 index 0000000..54026c9 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/dialog/dialog-overlay.svelte b/src/lib/components/ui/dialog/dialog-overlay.svelte new file mode 100644 index 0000000..f81ad83 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-overlay.svelte @@ -0,0 +1,20 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-portal.svelte b/src/lib/components/ui/dialog/dialog-portal.svelte new file mode 100644 index 0000000..ccfa79c --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-title.svelte b/src/lib/components/ui/dialog/dialog-title.svelte new file mode 100644 index 0000000..e4d4b34 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-title.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog-trigger.svelte b/src/lib/components/ui/dialog/dialog-trigger.svelte new file mode 100644 index 0000000..9d1e801 --- /dev/null +++ b/src/lib/components/ui/dialog/dialog-trigger.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/dialog.svelte b/src/lib/components/ui/dialog/dialog.svelte new file mode 100644 index 0000000..211672c --- /dev/null +++ b/src/lib/components/ui/dialog/dialog.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/dialog/index.ts b/src/lib/components/ui/dialog/index.ts new file mode 100644 index 0000000..076cef5 --- /dev/null +++ b/src/lib/components/ui/dialog/index.ts @@ -0,0 +1,34 @@ +import Root from "./dialog.svelte"; +import Portal from "./dialog-portal.svelte"; +import Title from "./dialog-title.svelte"; +import Footer from "./dialog-footer.svelte"; +import Header from "./dialog-header.svelte"; +import Overlay from "./dialog-overlay.svelte"; +import Content from "./dialog-content.svelte"; +import Description from "./dialog-description.svelte"; +import Trigger from "./dialog-trigger.svelte"; +import Close from "./dialog-close.svelte"; + +export { + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose, +}; diff --git a/src/lib/components/ui/radio-group/index.ts b/src/lib/components/ui/radio-group/index.ts new file mode 100644 index 0000000..90b33fe --- /dev/null +++ b/src/lib/components/ui/radio-group/index.ts @@ -0,0 +1,10 @@ +import Root from "./radio-group.svelte"; +import Item from "./radio-group-item.svelte"; + +export { + Root, + Item, + // + Root as RadioGroup, + Item as RadioGroupItem, +}; diff --git a/src/lib/components/ui/radio-group/radio-group-item.svelte b/src/lib/components/ui/radio-group/radio-group-item.svelte new file mode 100644 index 0000000..f0813db --- /dev/null +++ b/src/lib/components/ui/radio-group/radio-group-item.svelte @@ -0,0 +1,31 @@ + + + + {#snippet children({ checked })} +
+ {#if checked} + + {/if} +
+ {/snippet} +
diff --git a/src/lib/components/ui/radio-group/radio-group.svelte b/src/lib/components/ui/radio-group/radio-group.svelte new file mode 100644 index 0000000..da2912b --- /dev/null +++ b/src/lib/components/ui/radio-group/radio-group.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/lib/components/ui/select/index.ts b/src/lib/components/ui/select/index.ts new file mode 100644 index 0000000..4dec358 --- /dev/null +++ b/src/lib/components/ui/select/index.ts @@ -0,0 +1,37 @@ +import Root from "./select.svelte"; +import Group from "./select-group.svelte"; +import Label from "./select-label.svelte"; +import Item from "./select-item.svelte"; +import Content from "./select-content.svelte"; +import Trigger from "./select-trigger.svelte"; +import Separator from "./select-separator.svelte"; +import ScrollDownButton from "./select-scroll-down-button.svelte"; +import ScrollUpButton from "./select-scroll-up-button.svelte"; +import GroupHeading from "./select-group-heading.svelte"; +import Portal from "./select-portal.svelte"; + +export { + Root, + Group, + Label, + Item, + Content, + Trigger, + Separator, + ScrollDownButton, + ScrollUpButton, + GroupHeading, + Portal, + // + Root as Select, + Group as SelectGroup, + Label as SelectLabel, + Item as SelectItem, + Content as SelectContent, + Trigger as SelectTrigger, + Separator as SelectSeparator, + ScrollDownButton as SelectScrollDownButton, + ScrollUpButton as SelectScrollUpButton, + GroupHeading as SelectGroupHeading, + Portal as SelectPortal, +}; diff --git a/src/lib/components/ui/select/select-content.svelte b/src/lib/components/ui/select/select-content.svelte new file mode 100644 index 0000000..4b9ca43 --- /dev/null +++ b/src/lib/components/ui/select/select-content.svelte @@ -0,0 +1,45 @@ + + + + + + + {@render children?.()} + + + + diff --git a/src/lib/components/ui/select/select-group-heading.svelte b/src/lib/components/ui/select/select-group-heading.svelte new file mode 100644 index 0000000..1fab5f0 --- /dev/null +++ b/src/lib/components/ui/select/select-group-heading.svelte @@ -0,0 +1,21 @@ + + + + {@render children?.()} + diff --git a/src/lib/components/ui/select/select-group.svelte b/src/lib/components/ui/select/select-group.svelte new file mode 100644 index 0000000..a1f43bf --- /dev/null +++ b/src/lib/components/ui/select/select-group.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/select/select-item.svelte b/src/lib/components/ui/select/select-item.svelte new file mode 100644 index 0000000..b85eef6 --- /dev/null +++ b/src/lib/components/ui/select/select-item.svelte @@ -0,0 +1,38 @@ + + + + {#snippet children({ selected, highlighted })} + + {#if selected} + + {/if} + + {#if childrenProp} + {@render childrenProp({ selected, highlighted })} + {:else} + {label || value} + {/if} + {/snippet} + diff --git a/src/lib/components/ui/select/select-label.svelte b/src/lib/components/ui/select/select-label.svelte new file mode 100644 index 0000000..4696025 --- /dev/null +++ b/src/lib/components/ui/select/select-label.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/lib/components/ui/select/select-portal.svelte b/src/lib/components/ui/select/select-portal.svelte new file mode 100644 index 0000000..424bcdd --- /dev/null +++ b/src/lib/components/ui/select/select-portal.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/components/ui/select/select-scroll-down-button.svelte b/src/lib/components/ui/select/select-scroll-down-button.svelte new file mode 100644 index 0000000..3629205 --- /dev/null +++ b/src/lib/components/ui/select/select-scroll-down-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/src/lib/components/ui/select/select-scroll-up-button.svelte b/src/lib/components/ui/select/select-scroll-up-button.svelte new file mode 100644 index 0000000..1aa2300 --- /dev/null +++ b/src/lib/components/ui/select/select-scroll-up-button.svelte @@ -0,0 +1,20 @@ + + + + + diff --git a/src/lib/components/ui/select/select-separator.svelte b/src/lib/components/ui/select/select-separator.svelte new file mode 100644 index 0000000..0eac3eb --- /dev/null +++ b/src/lib/components/ui/select/select-separator.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/lib/components/ui/select/select-trigger.svelte b/src/lib/components/ui/select/select-trigger.svelte new file mode 100644 index 0000000..dbb81df --- /dev/null +++ b/src/lib/components/ui/select/select-trigger.svelte @@ -0,0 +1,29 @@ + + + + {@render children?.()} + + diff --git a/src/lib/components/ui/select/select.svelte b/src/lib/components/ui/select/select.svelte new file mode 100644 index 0000000..05eb663 --- /dev/null +++ b/src/lib/components/ui/select/select.svelte @@ -0,0 +1,11 @@ + + + diff --git a/src/lib/components/ui/textarea/index.ts b/src/lib/components/ui/textarea/index.ts new file mode 100644 index 0000000..ace797a --- /dev/null +++ b/src/lib/components/ui/textarea/index.ts @@ -0,0 +1,7 @@ +import Root from "./textarea.svelte"; + +export { + Root, + // + Root as Textarea, +}; diff --git a/src/lib/components/ui/textarea/textarea.svelte b/src/lib/components/ui/textarea/textarea.svelte new file mode 100644 index 0000000..124e9d0 --- /dev/null +++ b/src/lib/components/ui/textarea/textarea.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/lib/consts.ts b/src/lib/consts.ts index c29f166..c78747b 100644 --- a/src/lib/consts.ts +++ b/src/lib/consts.ts @@ -7,3 +7,22 @@ export const PERMISSIONS = { }; export const EXPIRE_REMINDER_DAYS = 30; + +// const EMAIL_REGEX = new RegExp( +// // eslint-disable-next-line no-control-regex +// "([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\"\(\[\]!#-[^-~ \t]|(\\[\t -~]))+\")@([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\[[\t -Z^-~]*])" +// ); + +// const EMAIL_REGEX = new RegExp( +// /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ +// ); + +// const EMAIL_REGEX = +// // eslint-disable-next-line no-control-regex +// /(?:[a-z0-9!#$%&'*+\/=?^`\{-\}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`\{-\}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; + +const EMAIL_REGEX = + /^(?!\.)(?!.*\.\.)([a-z0-9_'+\-.]*)[a-z0-9_'+-]@([a-z0-9][a-z0-9-]*\.)+[a-z]{2,}$/i; + +// Replace single quote with HTML entity or remove it from the character class +export const EMAIL_REGEX_STRING = EMAIL_REGEX.source.replace(/'/g, '''); diff --git a/src/lib/db/db.server.ts b/src/lib/db/db.server.ts index 1113bc1..9109d8d 100644 --- a/src/lib/db/db.server.ts +++ b/src/lib/db/db.server.ts @@ -8,7 +8,8 @@ const sql = postgres({ port: parseInt(process.env.POSTGRES_PORT!), database: process.env.POSTGRES_DB, username: process.env.POSTGRES_USER, - password: process.env.POSTGRES_PASSWORD + password: process.env.POSTGRES_PASSWORD, + transform: postgres.camel }); export default sql; diff --git a/src/lib/db/users.server.ts b/src/lib/db/users.server.ts index 327e483..e69de29 100644 --- a/src/lib/db/users.server.ts +++ b/src/lib/db/users.server.ts @@ -1,67 +0,0 @@ -import type { User } from '$lib/types'; -import sql from '$lib/db/db.server'; -import bcrypt from 'bcrypt'; - -// should require MANAGE_USERS permission - -export async function getUsers(searchQuery: string | null = null): Promise { - return sql` - SELECT id, - email, - perms, - name, - created_at AT TIME ZONE 'UTC' AS "createdAt", - last_signin AT TIME ZONE 'UTC' AS "lastSignIn" - FROM users - WHERE (${!searchQuery ? sql`TRUE` : sql`email ILIKE ${'%' + searchQuery + '%'} OR name ILIKE ${'%' + searchQuery + '%'}`}); - `; -} - -export async function getUser(id: number): Promise { - return ( - await sql` - SELECT id, - email, - perms, - name, - created_at AT TIME ZONE 'UTC' AS "createdAt", - last_signin AT TIME ZONE 'UTC' AS "lastSignIn" - FROM users - WHERE (id = ${id}) LIMIT 1; - ` - )[0]; -} - -export async function createUser(user: User) { - const passwordHash = await bcrypt.hash(user.password!, 12); - await sql` - INSERT INTO users (email, password_hash, perms, name, created_at, last_signin) - VALUES (${user.email}, ${passwordHash}, ${user.perms}, ${user.name}, NOW(), NOW()) - RETURNING id; - `; -} - -export async function editUser(user: User) { - let passwordHash: string | undefined; - if (user.password) { - passwordHash = await bcrypt.hash(user.password, 12); - } - - await sql` - UPDATE users - SET - email = ${user.email}, - ${passwordHash ? sql`password_hash = ${passwordHash},` : sql``} - perms = ${user.perms}, - name = ${user.name} - WHERE id = ${user.id!} - RETURNING id; - `; -} - -export async function deleteUser(id: number) { - await sql` - DELETE FROM users - WHERE id = ${id} - `; -} diff --git a/src/lib/email/sender.server.ts b/src/lib/email/sender.server.ts new file mode 100644 index 0000000..8a986b8 --- /dev/null +++ b/src/lib/email/sender.server.ts @@ -0,0 +1,23 @@ +import nodemailer from 'nodemailer'; + +// Create a transporter object using SMTP transport +const transporter = nodemailer.createTransport({ + host: process.env.EMAIL_HOST, + port: Number(process.env.EMAIL_PORT), + secure: true, // true for 465, false for other ports + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS + } +}); + +export async function sendEmployerNotificationEmail() { + // Send mail with defined transport object + await transporter.sendMail({ + from: `CareerConnect Notifications <${process.env.EMAIL_USER}>`, + // to: info.emails.join(', '), // EMAILING OF REAL COMPANIES DISABLED, UNCOMMENT TO ENABLE + to: 'drake@marinodev.com', // TEMPORARY EMAIL FOR TESTING + subject: 'New Application Received!', + text: `A new application has been received for the posting ''!\n\nCheck it out at ${process.env.BASE_URL}/` + }); +} diff --git a/src/lib/shared.ts b/src/lib/shared.ts index 3fbc28a..6faa9c3 100644 --- a/src/lib/shared.ts +++ b/src/lib/shared.ts @@ -1,4 +1,5 @@ -import type { User } from '$lib/types'; +import type { User } from '$lib/types/user'; +import { fail } from '@sveltejs/kit'; export const getCookieValue = (name: string): string => document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || ''; @@ -21,15 +22,21 @@ export function getFormString(data: FormData, key: string): string | undefined { } if (typeof value !== 'string') { - throw Error(`Incorrect input in field ${key}.`); + throw fail(400, { + error: `Incorrect input in field ${key}.`, + success: false + }); } return value.trim(); } export function getRequiredFormString(data: FormData, key: string) { const value = data.get(key); - if (typeof value !== 'string') { - throw Error(`Missing required field ${key}.`); + if (typeof value !== 'string' || value === '') { + throw fail(400, { + error: `Missing required field ${key}.`, + success: false + }); } return value.trim(); } diff --git a/src/lib/types.ts b/src/lib/types.ts deleted file mode 100644 index 8be041a..0000000 --- a/src/lib/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface User { - id: number; - email: string; - phone: string; - password?: string; - name: string; - createdAt: Date; - lastSignIn: Date; -} - -export interface Item { - id: number; - ownerEmail: string; - ownerPhone: string; - foundDate: Date; - approvedDate?: Date; - claimedDate?: Date; - title: string; - description: string; - transferred: boolean; // to L&F location - keywords?: string[]; -} diff --git a/src/lib/types/inquiries.server.ts b/src/lib/types/inquiries.server.ts new file mode 100644 index 0000000..374d72b --- /dev/null +++ b/src/lib/types/inquiries.server.ts @@ -0,0 +1,13 @@ +export enum Sender { + ADMIN = 'admin', + FINDER = 'finder', + INQUIRER = 'inquirer' +} + +export interface message { + id: number; + threadId: number; + sender: Sender; + body: string; + createdAt: Date; +} diff --git a/src/lib/types/item.d.ts b/src/lib/types/item.d.ts new file mode 100644 index 0000000..61224b3 --- /dev/null +++ b/src/lib/types/item.d.ts @@ -0,0 +1,13 @@ +export interface Item { + id: number; + emails?: string[]; + // ownerPhone: string; + foundDate: Date; + approvedDate?: Date; + claimedDate?: Date; + // title: string; + description: string; + transferred: boolean; // to L&F location + keywords?: string[]; + foundLocation: string; +} diff --git a/src/lib/types/user.ts b/src/lib/types/user.ts new file mode 100644 index 0000000..3ad6537 --- /dev/null +++ b/src/lib/types/user.ts @@ -0,0 +1,30 @@ +export interface User { + id: number; + username: string; + email: string; + name: string; + password?: string; + password_hash?: string; + settings?: UserSettings; + createdAt: Date; + lastSignIn: Date; +} + +export interface UserPayload { + id: number; + email: string; + username: string; + name: string; +} + +export interface UserSettings { + staleItemDays: number; + notifyAllApprovedInquiries?: boolean; + notifyAllTurnedInInquiries?: boolean; +} + +export const DefaultUserSettings: UserSettings = { + staleItemDays: 30, + notifyAllApprovedInquiries: false, + notifyAllTurnedInInquiries: false +}; diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 0000000..e18c66d --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,34 @@ + + +
+

+ {page.status} +

+

That's an error

+ {#if page.status === 404} +

We cant seem to find the page you are looking for.

+

The address may be mistyped, or the page may have moved or been deleted.

+ {/if} + {#if page.status === 403} +

You dont have access to this page!

+

Please contact your admin if you think this is a mistake

+ {/if} + {#if page.status === 401} +

You must be signed-in to view this page!

+ {/if} + {#if page.status === 500} +

This one is on our end...

+

We are working to resolve this as fast as possible.

+ {/if} + {#if page.status === 400} +

Invalid request!

+

Make sure you have correctly inputted all information.

+ {/if} + {#if page.status !== 404 && page.status !== 403 && page.status !== 401 && page.status !== 500 && page.status !== 400} +

An unexpected error has occurred.

+

Please try again later

+ {/if} +

Error: {page.error?.message}

+
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index ff05451..c9f609d 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,10 +1,5 @@ import type { LayoutServerLoad } from './$types'; -import { getUserFromJWT } from '$lib/shared'; -export const load: LayoutServerLoad = ({ cookies }) => { - const jwt = cookies.get('jwt'); - if (jwt) { - return { selfUser: getUserFromJWT(jwt) }; - } - return { selfUser: null }; +export const load: LayoutServerLoad = async ({ locals }) => { + return { user: locals.user }; }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b87eae6..9254310 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,29 +1,59 @@ - + - - - - - - - - - - +
-
- {@render children?.()} +
+ +
+ + + + +
+ Admin Log in +
+ +
+
+
+
+
+ +
+
+ {@render children?.()} +
+
+
+ +
+

+ {new Date().getFullYear()} MarinoDev. Git Repository +

+ + + + +
- -
- -
diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..79c63ce --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,89 @@ +import type { Actions, PageServerLoad } from './$types'; +import { getFormString, getRequiredFormString } from '$lib/shared'; +import { fail } from '@sveltejs/kit'; +import path from 'path'; +import sql from '$lib/db/db.server'; +import sharp from 'sharp'; +import { writeFileSync } from 'node:fs'; +import type { Item } from '$lib/types/item'; + +export const load: PageServerLoad = async () => { + const items: Item[] = await sql`SELECT * FROM items;`; + return { + items + }; +}; + +export const actions = { + create: async ({ request }) => { + const data = await request.formData(); + + const description = getRequiredFormString(data, 'description'); + const foundLocation = getFormString(data, 'foundLocation') || null; + const email = getFormString(data, 'email') || null; + const location = getFormString(data, 'location') || null; + const file = data.get('image')!; + + if (!email && location !== 'turnedIn') { + fail(400, { + error: "Email is required if it is still in finder's possession", + success: false + }); + } + + if (!(file instanceof File)) { + return fail(400, { error: 'No file uploaded or file is invalid', success: false }); + } + + // Convert File → Buffer + const inputBuffer = Buffer.from(await file.arrayBuffer()); + + // Detect format (Sharp does this internally) + const image = sharp(inputBuffer); + const metadata = await image.metadata(); + + let outputBuffer: Buffer; + + if (metadata.format === 'jpeg') { + // Already JPG → keep as-is + outputBuffer = inputBuffer; + } else { + // Convert to JPG + outputBuffer = await image + .jpeg({ quality: 90 }) // adjust if needed + .toBuffer(); + } + + let emailList = null; + if (email) { + emailList = [email]; + } + + const response = await sql` + INSERT INTO items ( + email, + description, + transferred, + found_location + ) VALUES ( + ${emailList}, + ${description}, + ${location === 'turnedIn'}, + ${foundLocation} + ) + RETURNING id; + `; + + try { + // It's a good idea to validate the filename to prevent path traversal attacks + const savePath = path.join('uploads', `${response[0]['id']}.jpg`); + + writeFileSync(savePath, outputBuffer); + } catch (err) { + console.error('File upload failed:', err); + return fail(500, { error: 'Internal server error during file upload', success: false }); + } + + return { success: true }; + } +} satisfies Actions; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8790510..a934d05 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,6 +1,111 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + +
+
+

Found Items

+
+ + Post an item + + + + Submit Found Item + + Your item will need to be approved before becoming public. + + +
+ + + + + Description* + + + + + + Where did you find it? + + + + +
+ + +
+
+ + +
+
+ + + Your Email + + + + +
+ + + Cancel + + + +
+
+
+
+
+ +
+ {#each data.items as item (item.id)} + + {/each} + +
+
+{#if isGenerating} +
+

Loading...

+
+{/if} + diff --git a/src/routes/create/+page.server.ts b/src/routes/create/+page.server.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/create/+page.svelte b/src/routes/create/+page.svelte new file mode 100644 index 0000000..3f30ad2 --- /dev/null +++ b/src/routes/create/+page.svelte @@ -0,0 +1,143 @@ + + +
+
+ + + Submit Found Item + Your item will need to be approved before becoming public + + + + + Image + + + + + Name on Card + + + +
+ + + Card Number + + + Enter your 16-digit number. + + + CVV + + +
+
+ + Month + + + + {month || "MM"} + + + + 01 + 02 + 03 + 04 + 05 + 06 + 07 + 08 + 09 + 10 + 11 + 12 + + + + + Year + + + + {year || "YYYY"} + + + + 2024 + 2025 + 2026 + 2027 + 2028 + 2029 + + + +
+
+
+ + + Billing Address + + The billing address associated with your payment method + + + + + + Same as shipping address + + + + + + + + + Comments + +