Compare commits
No commits in common. "cfde960e63dfd7514f1c922ed7b1594931dfb99f" and "28c18e249cb9a95c416b66344b7f27e549f2c9be" have entirely different histories.
cfde960e63
...
28c18e249c
139
package-lock.json
generated
139
package-lock.json
generated
@ -12,10 +12,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsonwebtoken": "^9.0.2",
|
|
||||||
"postgres": "^3.4.5",
|
|
||||||
"vitest": "^2.0.4"
|
"vitest": "^2.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -24,7 +21,6 @@
|
|||||||
"@sveltejs/kit": "^2.9.0",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.7",
|
|
||||||
"eslint": "^9.7.0",
|
"eslint": "^9.7.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
@ -1308,16 +1304,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/jsonwebtoken": {
|
|
||||||
"version": "9.0.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
|
|
||||||
"integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.10.5",
|
"version": "22.10.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
|
||||||
@ -1945,12 +1931,6 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer-equal-constant-time": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
|
||||||
"license": "BSD-3-Clause"
|
|
||||||
},
|
|
||||||
"node_modules/cac": {
|
"node_modules/cac": {
|
||||||
"version": "6.7.14",
|
"version": "6.7.14",
|
||||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||||
@ -2230,33 +2210,12 @@
|
|||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
|
||||||
"version": "16.4.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
|
||||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://dotenvx.com"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/ecdsa-sig-formatter": {
|
|
||||||
"version": "1.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
|
||||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.71",
|
"version": "1.5.71",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz",
|
||||||
@ -3220,49 +3179,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/jsonwebtoken": {
|
|
||||||
"version": "9.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
|
||||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"jws": "^3.2.2",
|
|
||||||
"lodash.includes": "^4.3.0",
|
|
||||||
"lodash.isboolean": "^3.0.3",
|
|
||||||
"lodash.isinteger": "^4.0.4",
|
|
||||||
"lodash.isnumber": "^3.0.3",
|
|
||||||
"lodash.isplainobject": "^4.0.6",
|
|
||||||
"lodash.isstring": "^4.0.1",
|
|
||||||
"lodash.once": "^4.0.0",
|
|
||||||
"ms": "^2.1.1",
|
|
||||||
"semver": "^7.5.4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12",
|
|
||||||
"npm": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jwa": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"buffer-equal-constant-time": "1.0.1",
|
|
||||||
"ecdsa-sig-formatter": "1.0.11",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/jws": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
|
||||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"jwa": "^1.4.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
@ -3341,42 +3257,6 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.includes": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isboolean": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isinteger": {
|
|
||||||
"version": "4.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
|
||||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isnumber": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isplainobject": {
|
|
||||||
"version": "4.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
|
||||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isstring": {
|
|
||||||
"version": "4.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
|
||||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
@ -3384,12 +3264,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.once": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
|
||||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/loupe": {
|
"node_modules/loupe": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
|
||||||
@ -4063,19 +3937,6 @@
|
|||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/postgres": {
|
|
||||||
"version": "3.4.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.5.tgz",
|
|
||||||
"integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==",
|
|
||||||
"license": "Unlicense",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "individual",
|
|
||||||
"url": "https://github.com/sponsors/porsager"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
"@sveltejs/kit": "^2.9.0",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/jsonwebtoken": "^9.0.7",
|
|
||||||
"eslint": "^9.7.0",
|
"eslint": "^9.7.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.36.0",
|
"eslint-plugin-svelte": "^2.36.0",
|
||||||
@ -39,10 +38,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tailwindcss/forms": "^0.5.9",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"jsonwebtoken": "^9.0.2",
|
|
||||||
"postgres": "^3.4.5",
|
|
||||||
"vitest": "^2.0.4"
|
"vitest": "^2.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
## Table of Permissions
|
|
||||||
|
|
||||||
| Permission Value | Description |
|
|
||||||
|------------------|------------------------|
|
|
||||||
| `00000001` | View postings |
|
|
||||||
| `00000010` | View account |
|
|
||||||
| `00000100` | Submit postings access |
|
|
||||||
| `00001000` | Manage postings |
|
|
||||||
| `00010000` | Manage users |
|
|
||||||
| `00100000` | Apply |
|
|
||||||
43
src/app.css
43
src/app.css
@ -6,18 +6,12 @@
|
|||||||
--text-color: #000000;
|
--text-color: #000000;
|
||||||
--bg-color: #f4f4f4;
|
--bg-color: #f4f4f4;
|
||||||
--hover-bg-color: #e4e4f0;
|
--hover-bg-color: #e4e4f0;
|
||||||
--elevated-bg-color: #ffffff;
|
|
||||||
--low-emphasis-text-color: #6b6b6b;
|
|
||||||
--primary-color: #1F96F3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme='dark'] {
|
[data-theme='dark'] {
|
||||||
--text-color: #f4f4f4;
|
--text-color: #f4f4f4;
|
||||||
--bg-color: #080808;
|
--bg-color: #010101;
|
||||||
--hover-bg-color: #1f2937;
|
--hover-bg-color: #1f2937;
|
||||||
--elevated-bg-color: #1a202c;
|
|
||||||
--low-emphasis-text-color: #999999;
|
|
||||||
--primary-color: #1F96F3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -30,41 +24,24 @@ h1 {
|
|||||||
@apply text-4xl
|
@apply text-4xl
|
||||||
}
|
}
|
||||||
|
|
||||||
.elevated {
|
.nav-item {
|
||||||
background-color: var(--elevated-bg-color);
|
@apply rounded px-3 py-2 text-sm mr-1
|
||||||
@apply shadow-lg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover-bg-color:hover {
|
.nav-item:hover {
|
||||||
background-color: var(--hover-bg-color);
|
background-color: var(--hover-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.low-emphasis-text {
|
.nav-trailing {
|
||||||
color: var(--low-emphasis-text-color);
|
@apply rounded-full p-1
|
||||||
}
|
}
|
||||||
|
|
||||||
.low-emphasis-text:hover {
|
.nav-trailing:hover {
|
||||||
color: var(--text-color);
|
background-color: var(--hover-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.primary-underline {
|
.nav-logo:hover {
|
||||||
border-bottom: 2px solid var(--primary-color);
|
background-color: var(--hover-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-border {
|
|
||||||
border-top: 1px solid var(--elevated-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-border {
|
|
||||||
border-bottom: 1px solid var(--elevated-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-icon {
|
|
||||||
font-size: 24px !important;
|
|
||||||
font-variation-settings:
|
|
||||||
'FILL' 0,
|
|
||||||
'wght' 400,
|
|
||||||
'GRAD' 0,
|
|
||||||
'opsz' 20
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,5 @@
|
|||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config({ path: '.env' });
|
|
||||||
|
|
||||||
export const handle = async ({ event, resolve }) => {
|
export const handle = async ({ event, resolve }) => {
|
||||||
const theme = event.cookies.get('theme');
|
const theme = event.cookies.get('theme');
|
||||||
const JWT = event.cookies.get('jwt');
|
|
||||||
|
|
||||||
if (process.env.JWT_SECRET === undefined) {
|
|
||||||
throw new Error('JWT_SECRET not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (JWT) {
|
|
||||||
try {
|
|
||||||
const decoded = jwt.verify(JWT, process.env.JWT_SECRET);
|
|
||||||
} catch (err) {
|
|
||||||
event.cookies.delete('jwt', { path: '/' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!theme) {
|
if (!theme) {
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
import postgres from 'postgres';
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config({ path: '.env' });
|
|
||||||
|
|
||||||
const sql = postgres({
|
|
||||||
host: process.env.POSTGRES_HOST,
|
|
||||||
port: parseInt(process.env.POSTGRES_PORT!),
|
|
||||||
database: process.env.POSTGRES_DB,
|
|
||||||
username: process.env.POSTGRES_USER,
|
|
||||||
password: process.env.POSTGRES_PASSWORD
|
|
||||||
});
|
|
||||||
|
|
||||||
export default sql;
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import bcrypt from 'bcrypt';
|
|
||||||
import sql from '$lib/db/db.server';
|
|
||||||
|
|
||||||
export async function createUser(username: string, password: string): Promise<void> {
|
|
||||||
const password_hash: string = await bcrypt.hash(password, 12);
|
|
||||||
|
|
||||||
const response = await sql`
|
|
||||||
INSERT INTO users (username, password_hash, perms)
|
|
||||||
VALUES (${username}, ${password_hash}, 3);
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function checkUserCreds(username: string, password: string): Promise<number> {
|
|
||||||
const [user] = await sql`
|
|
||||||
SELECT password_hash, perms
|
|
||||||
FROM users
|
|
||||||
WHERE username = ${username}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (await bcrypt.compare(password, user.password_hash)) {
|
|
||||||
return user['perms'];
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
8
src/lib/db/index.ts
Normal file
8
src/lib/db/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
export async function createUser(username: string, password: string): Promise<void> {
|
||||||
|
const sql = `INSERT INTO users (username, password, perms)
|
||||||
|
VALUES ($username, $password, 0)`;
|
||||||
|
|
||||||
|
const hash = await bcrypt.hash(password, 12);
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
export let userState = $state({ perms: 0b00000001 });
|
|
||||||
@ -1,40 +1,32 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { userState } from '$lib/shared.svelte';
|
|
||||||
// import { userState } from '$lib/shared.svelte';
|
|
||||||
|
|
||||||
let currentTheme: string = $state('');
|
let currentTheme: string = $state('');
|
||||||
|
|
||||||
function toggleTheme(): void {
|
function toggleTheme(): void {
|
||||||
const theme = currentTheme === 'light' ? 'dark' : 'light';
|
const theme = currentTheme === 'light' ? 'dark' : 'light';
|
||||||
setTheme(theme);
|
set_theme(theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTheme(theme: string) {
|
function set_theme(theme: string) {
|
||||||
const one_year = 60 * 60 * 24 * 365;
|
const one_year = 60 * 60 * 24 * 365;
|
||||||
document.cookie = `theme=${theme}; max-age=${one_year}; path=/`;
|
document.cookie = `theme=${theme}; max-age=${one_year}; path=/`;
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
currentTheme = theme;
|
currentTheme = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCookieValue = (name: String) =>
|
|
||||||
document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)')?.pop() || '';
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const savedTheme = document.documentElement.getAttribute('data-theme');
|
const savedTheme = document.documentElement.getAttribute('data-theme');
|
||||||
if (savedTheme) {
|
if (savedTheme) {
|
||||||
currentTheme = savedTheme;
|
currentTheme = savedTheme;
|
||||||
} else {
|
return;
|
||||||
const darkPref: boolean = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
currentTheme = darkPref ? 'dark' : 'light';
|
|
||||||
setTheme(currentTheme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const JWT = getCookieValue('jwt');
|
const darkPref = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
if (JWT !== '') {
|
|
||||||
userState.perms = JSON.parse(atob(JWT.split('.')[1])).perms;
|
const theme = darkPref ? 'dark' : 'light';
|
||||||
}
|
set_theme(theme);
|
||||||
});
|
});
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
@ -42,12 +34,12 @@
|
|||||||
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..40,400,0,0&display=block&icon_names=account_circle,dark_mode,group,light_mode,login,sell,work"
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@40,400,0,0&icon_names=account_circle,dark_mode,light_mode,login"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="bottom-border mx-2 flex h-16 justify-between p-4 align-middle">
|
<div class="mx-2 flex h-14 justify-between p-3">
|
||||||
<nav class="pt-1">
|
<nav>
|
||||||
<a href="/" class="hover-bg-color mr-1 rounded-md px-2 pb-2 pt-1.5">
|
<a href="/" class="nav-logo mr-1 rounded-md px-2 pb-2 pt-1.5">
|
||||||
<img
|
<img
|
||||||
class="inline-block"
|
class="inline-block"
|
||||||
src="/mdevtriangle.svg"
|
src="/mdevtriangle.svg"
|
||||||
@ -56,38 +48,22 @@
|
|||||||
width="24"
|
width="24"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a href="/about" class="hover-bg-color mr-1 rounded px-3 py-2 text-sm">About</a>
|
<a href="/about" class="nav-item">About</a>
|
||||||
{#if (userState.perms & 0b00000001) !== 0}
|
<a href="/listings" class="nav-item">Listings</a>
|
||||||
<a href="/listings" class="hover-bg-color mr-1 rounded px-3 py-2 text-sm">Listings</a>
|
<a href="/administration" class="nav-item">Administration</a>
|
||||||
{/if}
|
|
||||||
{#if (userState.perms & 0b00001000) !== 0}
|
|
||||||
<a href="/administration/postings" class="hover-bg-color mr-1 rounded px-3 py-2 text-sm"
|
|
||||||
>Administration</a
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</nav>
|
</nav>
|
||||||
<div>
|
<div>
|
||||||
<button onclick={toggleTheme} class="">
|
<button onclick={toggleTheme} class="pr-2">
|
||||||
<span class="material-symbols-outlined rounded-full p-1 dark:invisible">
|
<span class="material-symbols-outlined nav-trailing">
|
||||||
{'light_mode'}
|
{currentTheme === 'dark' ? 'dark_mode' : 'light_mode'}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button onclick={toggleTheme} class="">
|
<button>
|
||||||
<span class="material-symbols-outlined invisible rounded-full p-1 dark:visible">
|
<span class="material-symbols-outlined nav-trailing">account_circle</span>
|
||||||
{'dark_mode'}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onclick={() =>
|
|
||||||
(window.location.href = (userState.perms & 0b00000010) !== 0 ? '/account' : '/signin')}
|
|
||||||
>
|
|
||||||
<span class="material-symbols-outlined rounded-full p-1">
|
|
||||||
{(userState.perms & 0b00000010) !== 0 ? 'account_circle' : 'login'}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="p-4">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,2 +1,6 @@
|
|||||||
<script lang="ts">
|
<h1>Hello world</h1>
|
||||||
</script>
|
|
||||||
|
<div class="text-center text-green-500">
|
||||||
|
<p class="text-red-800">Test Text</p>
|
||||||
|
<p>hello</p>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,5 +1 @@
|
|||||||
<h1>About</h1>
|
<p>hola</p>
|
||||||
<p>
|
|
||||||
This is my submission for the 2025 FBLA Website Coding & Development event. It was built using
|
|
||||||
<a href="https://svelte.dev/docs/kit/introduction" class="text-blue-600">SvelteKit</a>.
|
|
||||||
</p>
|
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import '../../app.css';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
let { children } = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="pt-2 text-center">
|
|
||||||
<a
|
|
||||||
href="/administration/postings"
|
|
||||||
class="p-2 {$page.url.pathname === '/administration/postings'
|
|
||||||
? 'primary-underline font-bold'
|
|
||||||
: 'low-emphasis-text'}"
|
|
||||||
><span class="material-symbols-outlined small-icon align-bottom">work</span> Postings</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/administration/users"
|
|
||||||
class="p-2 {$page.url.pathname === '/administration/users'
|
|
||||||
? 'primary-underline font-bold'
|
|
||||||
: 'low-emphasis-text'}"
|
|
||||||
><span class="material-symbols-outlined small-icon align-bottom">group</span> Users</a
|
|
||||||
>
|
|
||||||
<a
|
|
||||||
href="/administration/tags"
|
|
||||||
class="{$page.url.pathname === '/administration/tags'
|
|
||||||
? 'primary-underline font-bold'
|
|
||||||
: 'low-emphasis-text'} p-2"
|
|
||||||
><span class="material-symbols-outlined small-icon align-bottom">sell</span> Tags</a
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
import { checkUserCreds, createUser } from '$lib/db/index.server';
|
|
||||||
import { fail, redirect, type Actions, type Cookies } from '@sveltejs/kit';
|
|
||||||
import jwt from 'jsonwebtoken';
|
|
||||||
import * as dotenv from 'dotenv';
|
|
||||||
|
|
||||||
dotenv.config({ path: '.env' });
|
|
||||||
|
|
||||||
function setJWT(cookies: Cookies, username: string, perms: number) {
|
|
||||||
const payload = {
|
|
||||||
username: username,
|
|
||||||
perms: perms
|
|
||||||
};
|
|
||||||
|
|
||||||
if (process.env.JWT_SECRET === undefined) {
|
|
||||||
throw new Error('JWT_SECRET not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxAge = 60 * 60 * 24 * 30; // 30 days
|
|
||||||
const JWT = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '30d' });
|
|
||||||
cookies.set('jwt', JWT, { maxAge, path: '/', httpOnly: false });
|
|
||||||
console.log(cookies.get('jwt'));
|
|
||||||
}
|
|
||||||
|
|
||||||
export const actions: Actions = {
|
|
||||||
register: async ({ request, cookies }) => {
|
|
||||||
const data = await request.formData();
|
|
||||||
const username = data.get('username')?.toString();
|
|
||||||
const password = data.get('password')?.toString();
|
|
||||||
|
|
||||||
if (username && password) {
|
|
||||||
try {
|
|
||||||
await createUser(username, password);
|
|
||||||
} catch (err) {
|
|
||||||
return fail(400, { errorMessage: `Internal Server Error: ${err}` });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fail(400, { errorMessage: 'Missing username or password' });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
login: async ({ request, cookies }) => {
|
|
||||||
const data = await request.formData();
|
|
||||||
const username = data.get('username')?.toString();
|
|
||||||
const password = data.get('password')?.toString();
|
|
||||||
|
|
||||||
if (username && password) {
|
|
||||||
const perms = await checkUserCreds(username, password);
|
|
||||||
|
|
||||||
if (perms === -1) {
|
|
||||||
return fail(401, { errorMessage: 'Invalid username or password' });
|
|
||||||
}
|
|
||||||
|
|
||||||
setJWT(cookies, username, perms);
|
|
||||||
|
|
||||||
// redirect to home page
|
|
||||||
// return { perms: perms };
|
|
||||||
throw redirect(303, '/');
|
|
||||||
} else {
|
|
||||||
return fail(400, { errorMessage: 'Missing username or password' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { ActionData } from './$types';
|
|
||||||
|
|
||||||
// receive form data from server
|
|
||||||
let form: ActionData;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="is-size-3 has-text-weight-semibold my-4">Sign In or Register</h1>
|
|
||||||
<form method="POST">
|
|
||||||
<input class="input my-2" type="text" placeholder="Username" name="username" required />
|
|
||||||
<input class="input my-2" type="password" placeholder="Password" name="password" required />
|
|
||||||
|
|
||||||
<!-- display error message -->
|
|
||||||
{#if form?.errorMessage}
|
|
||||||
<div class="has-text-danger my-2">{form.errorMessage}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button class="button mr-3 mt-4" type="submit" formaction="?/register">Register</button>
|
|
||||||
<button class="button is-primary mt-4" type="submit" formaction="?/login">Sign In</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@ -2,13 +2,8 @@ import { defineConfig } from 'vitest/config';
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// @ts-ignore
|
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
|
|
||||||
server: {
|
|
||||||
host: true
|
|
||||||
},
|
|
||||||
|
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.{test,spec}.{js,ts}']
|
include: ['src/**/*.{test,spec}.{js,ts}']
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user