- prepare for external testing

This commit is contained in:
Markus Brueckner 2024-12-13 08:35:05 +01:00
parent 1547af6ae0
commit 7e9f7b5481
14 changed files with 389 additions and 47 deletions

22
Dockerfile Normal file
View file

@ -0,0 +1,22 @@
FROM docker.io/node:23 as builder
WORKDIR /build
COPY ./src ./src
COPY ./static ./static
COPY .npmrc package.json package-lock.json postcss.config.js svelte.config.js tailwind.config.ts tsconfig.json vite.config.ts ./
RUN npm ci
RUN npm run build
FROM docker.io/node:23
WORKDIR /app
COPY --from=builder /build/build ./
COPY --from=builder /build/package.json /build/package-lock.json ./
COPY ./entrypoint.sh ./drizzle-init.js ./
COPY ./drizzle ./
RUN npm ci --production
EXPOSE 3000
# ENTRYPOINT ["./entrypoint.sh"]
ENTRYPOINT ["node", "index.js"]

16
docker-compose.yml Normal file
View file

@ -0,0 +1,16 @@
services:
three60:
image: three60:latest
ports:
- 3000:3000
environment:
- DATABASE_URL=mysql://root:my-pwd@db:3306/three60
- ENABLE_REGISTER=true
- ALLOWABLE_DOMAINS=example.com
- INIT_DB=true
db:
image: docker.io/mariadb:latest
environment:
- MARIADB_ROOT_PASSWORD=my-pwd
- MARIADB_DATABASE=three60

275
package-lock.json generated
View file

@ -17,11 +17,13 @@
"d3-array": "^3.2.4", "d3-array": "^3.2.4",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",
"d3-shape": "^3.2.0", "d3-shape": "^3.2.0",
"drizzle-orm": "^0.36.3", "drizzle-orm": "^0.36.4",
"layerchart": "^0.59.1" "layerchart": "^0.59.1",
"mysql2": "^3.11.4"
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/d3-array": "^3.2.1", "@types/d3-array": "^3.2.1",
@ -1817,6 +1819,112 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@rollup/plugin-commonjs": {
"version": "28.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.1.tgz",
"integrity": "sha512-+tNWdlWKbpB3WgBN7ijjYkq9X5uhjmcvyjEght4NmH5fAU++zfQzAJ6wumLS+dNcvwEZhKx2Z+skY8m7v0wGSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"commondir": "^1.0.1",
"estree-walker": "^2.0.2",
"fdir": "^6.2.0",
"is-reference": "1.2.1",
"magic-string": "^0.30.3",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=16.0.0 || 14 >= 14.17"
},
"peerDependencies": {
"rollup": "^2.68.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@rollup/plugin-json": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.1.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz",
"integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
"integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.27.2", "version": "4.27.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.2.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.2.tgz",
@ -2082,6 +2190,22 @@
"@sveltejs/kit": "^2.0.0" "@sveltejs/kit": "^2.0.0"
} }
}, },
"node_modules/@sveltejs/adapter-node": {
"version": "5.2.9",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.9.tgz",
"integrity": "sha512-51euNrx0AcaTu8//wDfVh7xmqQSVgU52rfinE/MwvGkJa4nHPJMHmzv6+OIpmxg7gZaF6+5NVlxnieCzxLD59g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.3.0",
"rollup": "^4.9.5"
},
"peerDependencies": {
"@sveltejs/kit": "^2.4.0"
}
},
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.1.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.1.tgz",
@ -2273,6 +2397,13 @@
"undici-types": "~6.19.8" "undici-types": "~6.19.8"
} }
}, },
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.13", "version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
@ -2683,6 +2814,15 @@
"postcss": "^8.1.0" "postcss": "^8.1.0"
} }
}, },
"node_modules/aws-ssl-profiles": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@ -2882,6 +3022,13 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"dev": true,
"license": "MIT"
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -3321,6 +3468,15 @@
"robust-predicates": "^3.0.2" "robust-predicates": "^3.0.2"
} }
}, },
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz",
@ -3796,9 +3952,9 @@
} }
}, },
"node_modules/drizzle-orm": { "node_modules/drizzle-orm": {
"version": "0.36.3", "version": "0.36.4",
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.36.3.tgz", "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.36.4.tgz",
"integrity": "sha512-ffQB7CcyCTvQBK6xtRLMl/Jsd5xFTBs+UTHrgs1hbk68i5TPkbsoCPbKEwiEsQZfq2I7VH632XJpV1g7LS2H9Q==", "integrity": "sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"peerDependencies": { "peerDependencies": {
"@aws-sdk/client-rds-data": ">=3", "@aws-sdk/client-rds-data": ">=3",
@ -3806,7 +3962,7 @@
"@electric-sql/pglite": ">=0.2.0", "@electric-sql/pglite": ">=0.2.0",
"@libsql/client": ">=0.10.0", "@libsql/client": ">=0.10.0",
"@libsql/client-wasm": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0",
"@neondatabase/serverless": ">=0.1", "@neondatabase/serverless": ">=0.10.0",
"@op-engineering/op-sqlite": ">=2", "@op-engineering/op-sqlite": ">=2",
"@opentelemetry/api": "^1.4.1", "@opentelemetry/api": "^1.4.1",
"@planetscale/database": ">=1", "@planetscale/database": ">=1",
@ -4252,6 +4408,13 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true,
"license": "MIT"
},
"node_modules/esutils": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@ -4501,6 +4664,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/generate-function": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
"license": "MIT",
"dependencies": {
"is-property": "^1.0.2"
}
},
"node_modules/get-tsconfig": { "node_modules/get-tsconfig": {
"version": "4.8.1", "version": "4.8.1",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
@ -4762,6 +4934,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"dev": true,
"license": "MIT"
},
"node_modules/is-number": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@ -4771,6 +4950,12 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-property": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"license": "MIT"
},
"node_modules/is-reference": { "node_modules/is-reference": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
@ -5037,12 +5222,33 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/long": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
"integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
"license": "Apache-2.0"
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/lru.min": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz",
"integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==",
"license": "MIT",
"engines": {
"bun": ">=1.0.0",
"deno": ">=1.30.0",
"node": ">=8.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wellwelwel"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.12", "version": "0.30.12",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz",
@ -5144,6 +5350,26 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mysql2": {
"version": "3.11.4",
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.4.tgz",
"integrity": "sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==",
"license": "MIT",
"dependencies": {
"aws-ssl-profiles": "^1.1.1",
"denque": "^2.1.0",
"generate-function": "^2.3.1",
"iconv-lite": "^0.6.3",
"long": "^5.2.1",
"lru.min": "^1.0.0",
"named-placeholders": "^1.1.3",
"seq-queue": "^0.0.5",
"sqlstring": "^2.3.2"
},
"engines": {
"node": ">= 8.0"
}
},
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -5155,6 +5381,27 @@
"thenify-all": "^1.0.0" "thenify-all": "^1.0.0"
} }
}, },
"node_modules/named-placeholders": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz",
"integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==",
"license": "MIT",
"dependencies": {
"lru-cache": "^7.14.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/named-placeholders/node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@ -5393,8 +5640,6 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -5964,6 +6209,11 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/seq-queue": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
"integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
},
"node_modules/set-cookie-parser": { "node_modules/set-cookie-parser": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
@ -6079,6 +6329,15 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/sqlstring": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
"integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stream-source": { "node_modules/stream-source": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz", "resolved": "https://registry.npmjs.org/stream-source/-/stream-source-0.3.5.tgz",

View file

@ -13,6 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0", "@sveltejs/vite-plugin-svelte": "^4.0.0",
"@types/d3-array": "^3.2.1", "@types/d3-array": "^3.2.1",
@ -45,7 +46,8 @@
"d3-array": "^3.2.4", "d3-array": "^3.2.4",
"d3-scale": "^4.0.2", "d3-scale": "^4.0.2",
"d3-shape": "^3.2.0", "d3-shape": "^3.2.0",
"drizzle-orm": "^0.36.3", "drizzle-orm": "^0.36.4",
"layerchart": "^0.59.1" "layerchart": "^0.59.1",
"mysql2": "^3.11.4"
} }
} }

View file

@ -1,7 +1,10 @@
import { drizzle } from 'drizzle-orm/libsql'; import { drizzle } from 'drizzle-orm/mysql2';
import * as schema from './schema'; import * as schema from './schema';
import { env } from '$env/dynamic/private';
export const db = drizzle({ export const db = drizzle(
connection: 'file:db.sqlite', env.DATABASE_URL ?? '',
schema {
}); schema,
mode: 'default'
});

View file

@ -1,41 +1,41 @@
import { index, int, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { index, int, primaryKey, mysqlTable, text, varchar, bigint } from "drizzle-orm/mysql-core";
export const usersTable = sqliteTable("users_table", { export const usersTable = mysqlTable("users_table", {
id: int().primaryKey({ autoIncrement: true }), id: int().autoincrement().primaryKey(),
email: text().notNull().unique(), email: text().notNull().unique(),
password_hash: text().notNull(), password_hash: text().notNull(),
}); });
export const sessions = sqliteTable("sessions", { export const sessions = mysqlTable("sessions", {
token: text().notNull().primaryKey(), token: varchar({ length: 255 }).notNull().primaryKey(),
userId: int().notNull().references(() => usersTable.id, { onDelete: "cascade" }), userId: int().notNull().references(() => usersTable.id, { onDelete: "cascade" }),
expires: int().notNull(), expires: bigint({ mode: "number" }).notNull(),
}); });
export const surveysTable = sqliteTable("surveys_table", { export const surveysTable = mysqlTable("surveys_table", {
id: int().primaryKey({ autoIncrement: true }), id: int().autoincrement().primaryKey(),
title: text().notNull(), title: text().notNull(),
description: text(), description: text(),
owner: int().notNull().references(() => usersTable.id, { onDelete: "cascade" }), owner: int().notNull().references(() => usersTable.id, { onDelete: "cascade" }),
}); });
export const surveySkillsTable = sqliteTable("survey_skills_table", { export const surveySkillsTable = mysqlTable("survey_skills_table", {
id: int().primaryKey({ autoIncrement: true }), id: int().autoincrement().primaryKey(),
surveyId: int().notNull().references(() => surveysTable.id, { onDelete: "cascade" }), surveyId: int().notNull().references(() => surveysTable.id, { onDelete: "cascade" }),
title: text().notNull(), title: text().notNull(),
description: text(), description: text(),
}) })
export const surveyAccessTable = sqliteTable("survey_access_table", { export const surveyAccessTable = mysqlTable("survey_access_table", {
id: int().primaryKey({ autoIncrement: true }), id: int().autoincrement().primaryKey(),
surveyId: int().notNull().references(() => surveysTable.id, { onDelete: "cascade" }), surveyId: int().notNull().references(() => surveysTable.id, { onDelete: "cascade" }),
recepientEmail: text().notNull(), recepientEmail: text().notNull(),
accessToken: text().notNull(), accessToken: varchar({ length: 255 }).notNull(),
}, (table) => ({ }, (table) => ({
tokenIndex: index("token_index").on(table.accessToken), tokenIndex: index("token_index").on(table.accessToken),
})); }));
export const surveyAnswersTable = sqliteTable("survey_answers_table", { export const surveyAnswersTable = mysqlTable("survey_answers_table", {
participantId: int().notNull().references(() => surveyAccessTable.id, { onDelete: "cascade" }), participantId: int().notNull().references(() => surveyAccessTable.id, { onDelete: "cascade" }),
skillId: int().notNull().references(() => surveySkillsTable.id, { onDelete: "cascade" }), skillId: int().notNull().references(() => surveySkillsTable.id, { onDelete: "cascade" }),
rating: int().notNull(), rating: int().notNull(),

View file

@ -6,6 +6,7 @@
title: string; title: string;
description: string | null; description: string | null;
value: number; value: number;
noAnswer: boolean;
}; };
type Props = { skill: SkillProps }; type Props = { skill: SkillProps };
@ -29,8 +30,19 @@
bind:value={skill.value} bind:value={skill.value}
class="w-80 justify-self-start" class="w-80 justify-self-start"
list="values" list="values"
disabled={skill.noAnswer}
/> />
<label class="ml-2 text-xs">
<input
type="checkbox"
name="disable-{skill.id}"
bind:checked={skill.noAnswer}
class="text-xs"
/> No answer</label
>
{#if !skill.noAnswer}
<span class="ml-2">{skill.value}</span> <span class="ml-2">{skill.value}</span>
{/if}
</div> </div>
<datalist id="values"> <datalist id="values">
<option value="0" label="0"></option> <option value="0" label="0"></option>

View file

@ -1,4 +1,3 @@
import { encodeBase32LowerCaseNoPadding } from '@oslojs/encoding';
import { db } from '../../db'; import { db } from '../../db';
import { sessions } from '../../db/schema'; import { sessions } from '../../db/schema';
import { eq, lt } from 'drizzle-orm'; import { eq, lt } from 'drizzle-orm';

View file

@ -45,7 +45,7 @@ export const actions = {
error(400, 'At least one skill is required'); error(400, 'At least one skill is required');
} }
const ids = await db.insert(surveysTable).values({ title, description, owner }).returning({ id: surveysTable.id }); const ids = await db.insert(surveysTable).values({ title, description, owner }).$returningId();
const surveyId = ids[0].id; const surveyId = ids[0].id;
for (const participant of participants) { for (const participant of participants) {

View file

@ -56,7 +56,7 @@ export const actions = {
const formData = await request.formData(); const formData = await request.formData();
// validate that the form doesn't contain invalid skill IDs // validate that the form doesn't contain invalid skill IDs
const skillEntries = [...formData.entries()]; const skillEntries = [...formData.entries()].filter(([key, _]) => key.startsWith('disable-'));
const allIdsValid = skillEntries.every(([key, _]) => skills.some(skill => skill.id.toString() === key)); const allIdsValid = skillEntries.every(([key, _]) => skills.some(skill => skill.id.toString() === key));
if (!allIdsValid || skillEntries.length !== skills.length) { if (!allIdsValid || skillEntries.length !== skills.length) {
error(400, 'Invalid skill ID'); error(400, 'Invalid skill ID');

View file

@ -10,7 +10,8 @@
id: skill.id.toString(), id: skill.id.toString(),
title: skill.title, title: skill.title,
description: skill.description, description: skill.description,
value: 0 value: 0,
noAnswer: false
})) }))
); );
</script> </script>
@ -27,13 +28,15 @@
<button type="submit" class="mt-5 w-40 bg-slate-200">Submit</button> <button type="submit" class="mt-5 w-40 bg-slate-200">Submit</button>
</form> </form>
<dl class="ml-8 mt-5 grid grid-cols-[2rem_1fr] text-sm"> <dl class="ml-8 mt-5 grid grid-cols-[7rem_1fr] text-sm">
<dt class="font-semibold">0</dt> <dt class="text-center font-semibold">(No answer)</dt>
<dd>The person does not have this skill or I can't answer.</dd> <dd>I don't know and can't give useful answer.</dd>
<dt class="font-semibold">1</dt> <dt class="text-center font-semibold">0</dt>
<dd>The person does not have this skill.</dd>
<dt class="text-center font-semibold">1</dt>
<dd>The person can use this skill with guidance by more experienced people.</dd> <dd>The person can use this skill with guidance by more experienced people.</dd>
<dt class="font-semibold">2</dt> <dt class="text-center font-semibold">2</dt>
<dd>The person is able to use this skill by themselves.</dd> <dd>The person is able to use this skill by themselves.</dd>
<dt class="font-semibold">3</dt> <dt class="text-center font-semibold">3</dt>
<dd>The person is able to guide others to use this skill.</dd> <dd>The person is able to guide others to use this skill.</dd>
</dl> </dl>

View file

@ -1,14 +1,31 @@
import { hash } from '@node-rs/argon2'; import { hash } from '@node-rs/argon2';
import type { Actions } from './$types'; import type { Actions, PageServerLoad } from './$types';
import { usersTable } from '../../db/schema'; import { usersTable } from '../../db/schema';
import { db } from '../../db'; import { db } from '../../db';
import { redirect } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/private';
export const load: PageServerLoad = () => {
return {
allowableDomains: env.ALLOWABLE_DOMAINS
}
}
export const actions = { export const actions = {
default: async (event) => { default: async (event) => {
const allowedDomains = env.ALLOWABLE_DOMAINS?.split(',');
const formData = await event.request.formData(); const formData = await event.request.formData();
const email = formData.get('email'); const email = formData.get('email')?.toString();
const password = formData.get('password'); const password = formData.get('password')?.toString();
if (!email || !password) {
error(400, 'Email and password are required');
}
if (allowedDomains && !allowedDomains?.some(domain => email.endsWith(domain))) {
error(400, 'Invalid email domain');
}
const hashedPassword = await hash(password); const hashedPassword = await hash(password);
await db.insert(usersTable).values({ await db.insert(usersTable).values({

View file

@ -1,12 +1,21 @@
<script lang="ts"> <script lang="ts">
let password = $state(''); let password = $state('');
let password_repeat = $state(''); let password_repeat = $state('');
const { data } = $props();
</script> </script>
<h1 class="text-3xl">Register a new Account</h1> <h1 class="text-3xl">Register a new Account</h1>
<div> <div>
<form class="grid grid-cols-2 gap-1" method="POST"> <form class="grid grid-cols-2 gap-1" method="POST">
<label for="name" class="justify-self-end">Email address</label> <label for="name" class="justify-self-end"
><div>Email address</div>
{#if data.allowableDomains}
<div class="text-xs text-slate-500">
Allowed domains: {data.allowableDomains}
</div>
{/if}
</label>
<input type="email" name="email" id="email" class="justify-self-start" required /> <input type="email" name="email" id="email" class="justify-self-start" required />
<label for="password" class="justify-self-end">Password</label> <label for="password" class="justify-self-end">Password</label>
<input <input

View file

@ -1,4 +1,4 @@
import adapter from '@sveltejs/adapter-auto'; import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */