107 Commits

Author SHA1 Message Date
0cc24af26b 0.0.7
All checks were successful
Publish / lints (push) Successful in 29s
Publish / tests (push) Successful in 1m24s
Publish / Publishing (push) Successful in 1m41s
/ Misc Linters (push) Successful in 34s
/ Build App (push) Successful in 2m3s
2024-08-23 19:03:33 +02:00
c746f71622 Merge pull request 'chore(deps): update dependency svelte to v4.2.19' (#61) from renovate/svelte-4.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 37s
/ Build App (push) Successful in 1m35s
Reviewed-on: #61
2024-08-23 19:00:23 +02:00
b7e38dca12 chore(deps): update dependency svelte to v4.2.19
All checks were successful
/ Misc Linters (pull_request) Successful in 35s
/ Build App (pull_request) Successful in 1m43s
2024-08-23 16:56:16 +00:00
f098209b66 Merge pull request 'chore(deps): lock file maintenance' (#58) from renovate/lock-file-maintenance into main
All checks were successful
/ Misc Linters (push) Successful in 45s
/ Build App (push) Successful in 1m43s
Reviewed-on: #58
2024-08-23 18:53:20 +02:00
b5db49061e chore(deps): lock file maintenance
All checks were successful
/ Misc Linters (pull_request) Successful in 25s
/ Build App (pull_request) Successful in 1m48s
2024-08-23 18:44:14 +02:00
716799beb2 Merge pull request 'chore(deps): update dependency @sveltejs/vite-plugin-svelte to v3.1.2' (#59) from renovate/sveltejs-vite-plugin-svelte-3.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 30s
/ Build App (push) Successful in 1m49s
Reviewed-on: #59
2024-08-23 18:44:07 +02:00
a8c64f9d74 chore(deps): update dependency @sveltejs/vite-plugin-svelte to v3.1.2
All checks were successful
/ Misc Linters (pull_request) Successful in 30s
/ Build App (pull_request) Successful in 1m45s
2024-08-23 18:35:47 +02:00
fe7e1062d4 Merge pull request 'chore(deps): update dependency svelte-check to v3.8.6' (#60) from renovate/svelte-check-3.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 26s
/ Build App (push) Successful in 1m59s
Reviewed-on: #60
2024-08-23 18:35:38 +02:00
372d2b4253 chore(deps): update dependency svelte-check to v3.8.6
All checks were successful
/ Misc Linters (pull_request) Successful in 31s
/ Build App (pull_request) Successful in 1m37s
2024-08-22 19:02:02 +00:00
ad5705b00c Merge pull request 'chore(deps): update dependency vite to v5.4.2' (#57) from renovate/vite-5.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 24s
/ Build App (push) Successful in 1m39s
Reviewed-on: #57
2024-08-21 08:58:40 +02:00
868a518097 chore(deps): update dependency vite to v5.4.2
All checks were successful
/ Misc Linters (pull_request) Successful in 26s
/ Build App (pull_request) Successful in 1m34s
2024-08-20 16:02:01 +00:00
2836915aeb Merge pull request 'chore(deps): update dependency @sveltejs/kit to v2.5.24' (#55) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 24s
/ Build App (push) Successful in 1m45s
Reviewed-on: #55
2024-08-20 13:59:22 +02:00
353cc1bf1e Merge pull request 'chore(deps): update dependency typescript-eslint to v8.2.0' (#56) from renovate/typescript-eslint-monorepo into main
Some checks failed
/ Build App (push) Waiting to run
/ Misc Linters (push) Has been cancelled
Reviewed-on: #56
2024-08-20 13:59:12 +02:00
d60c1ddbbd Merge pull request 'chore(deps): update dependency @types/node to v20.16.1' (#54) from renovate/node-20.x-lockfile into main
Some checks failed
/ Build App (push) Has been cancelled
/ Misc Linters (push) Has been cancelled
Reviewed-on: #54
2024-08-20 13:58:58 +02:00
09a1e98afb chore(deps): update dependency typescript-eslint to v8.2.0
All checks were successful
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 1m27s
2024-08-19 19:01:56 +00:00
5c894e1462 chore(deps): update dependency @sveltejs/kit to v2.5.24
All checks were successful
/ Misc Linters (pull_request) Successful in 28s
/ Build App (pull_request) Successful in 1m36s
2024-08-19 13:01:38 +00:00
1eeec1215e chore(deps): update dependency @types/node to v20.16.1
All checks were successful
/ Misc Linters (pull_request) Successful in 18s
/ Build App (pull_request) Successful in 1m32s
2024-08-19 04:01:39 +00:00
d3016c765e Merge pull request 'chore(deps): update dependency @types/node to v20.16.0' (#52) from renovate/node-20.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 22s
/ Build App (push) Successful in 1m36s
Reviewed-on: #52
2024-08-18 16:03:16 +02:00
568da269a1 chore(deps): update dependency @types/node to v20.16.0
All checks were successful
/ Misc Linters (pull_request) Successful in 21s
/ Build App (pull_request) Successful in 1m20s
2024-08-18 15:51:05 +02:00
48b9a4c8fb Merge pull request 'Integration tests for API' (#53) from testing into main
All checks were successful
/ Misc Linters (push) Successful in 39s
/ Build App (push) Successful in 1m34s
Reviewed-on: #53
2024-08-18 15:48:51 +02:00
25ef50f358 Add test for page
All checks were successful
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 1m49s
2024-08-18 15:46:46 +02:00
20920586e0 Formatting
All checks were successful
/ Misc Linters (pull_request) Successful in 23s
/ Build App (pull_request) Successful in 1m21s
2024-08-18 15:38:03 +02:00
56f568634e Implement first integration tests
Some checks failed
/ Misc Linters (pull_request) Successful in 28s
/ Build App (pull_request) Failing after 27s
2024-08-18 15:34:36 +02:00
0fd1f178be Add playwright 2024-08-18 15:34:19 +02:00
da92ed6258 Move request-id header name to $lib 2024-08-18 15:33:56 +02:00
13b1d57e08 Merge pull request 'Healthcheck' (#51) from healthcheck into main
All checks were successful
/ Misc Linters (push) Successful in 31s
/ Build App (push) Successful in 1m8s
Reviewed-on: #51
2024-08-17 18:28:16 +02:00
1f69e739d0 Add healthcheck endpoint
All checks were successful
/ Misc Linters (pull_request) Successful in 18s
/ Build App (pull_request) Successful in 54s
2024-08-17 18:26:57 +02:00
58a65856aa Simplify
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 59s
2024-08-17 17:22:27 +02:00
e2a4335937 Define endpoint in TSy way
All checks were successful
/ Misc Linters (push) Successful in 25s
/ Build App (push) Successful in 52s
2024-08-17 17:12:05 +02:00
3160923f7e Fix typos and define handler in TSy way
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
2024-08-17 16:49:38 +02:00
37d6cd848d 0.0.6
All checks were successful
/ Misc Linters (push) Successful in 25s
Publish / lints (push) Successful in 25s
/ Build App (push) Successful in 1m1s
Publish / tests (push) Successful in 52s
Publish / Publishing (push) Successful in 1m30s
2024-08-17 16:43:34 +02:00
1aff6554b5 Copy node_modules over to runtime image
All checks were successful
/ Misc Linters (push) Successful in 20s
/ Build App (push) Successful in 52s
2024-08-17 16:37:29 +02:00
a6359d9516 Build using node 22 2024-08-17 16:37:13 +02:00
6ab52c1830 Fix lints
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
2024-08-17 15:54:33 +02:00
cfb5564830 Formatting with trailing comma 2024-08-17 15:45:52 +02:00
48948a180a Logging
Some checks failed
/ Misc Linters (push) Successful in 21s
/ Build App (push) Failing after 21s
2024-08-17 15:39:43 +02:00
1557d2cca9 More test cases 2024-08-17 15:38:23 +02:00
019d1d2e62 Log requests 2024-08-17 15:38:12 +02:00
1b4bb1d2e2 Measure execution time 2024-08-17 15:06:28 +02:00
824852fe7a Initialize and use bunyan logger 2024-08-17 15:06:12 +02:00
4e2b1b74a4 Add requestId to request context 2024-08-17 15:04:43 +02:00
0aa7a18f05 Add bunyan logger 2024-08-17 15:04:14 +02:00
10d6be5f82 Platform independent path validation
All checks were successful
/ Misc Linters (push) Successful in 21s
/ Build App (push) Successful in 51s
2024-08-17 15:00:54 +02:00
6615e2788a Platform independent path validation
All checks were successful
/ Misc Linters (push) Successful in 27s
/ Misc Linters (pull_request) Successful in 20s
/ Build App (pull_request) Successful in 58s
/ Build App (push) Successful in 47s
2024-08-17 14:59:55 +02:00
1f846bd5fe Merge pull request 'chore(deps): update dependency @types/node to v20.15.0' (#49) from renovate/node-20.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 34s
/ Build App (push) Successful in 1m1s
Reviewed-on: #49
2024-08-17 03:59:55 +02:00
da7b056095 chore(deps): update dependency @types/node to v20.15.0
All checks were successful
/ Misc Linters (pull_request) Successful in 19s
/ Build App (pull_request) Successful in 55s
2024-08-16 19:01:36 +00:00
4b8ecc65c3 0.0.5
All checks were successful
Publish / lints (push) Successful in 25s
Publish / tests (push) Successful in 51s
Publish / Publishing (push) Successful in 1m9s
/ Misc Linters (push) Successful in 26s
/ Build App (push) Successful in 1m1s
2024-08-16 18:18:02 +02:00
3696bcade2 Change parameter order 2024-08-16 18:17:44 +02:00
dea401fec0 Add title
All checks were successful
/ Misc Linters (push) Successful in 22s
/ Build App (push) Successful in 50s
2024-08-16 18:08:49 +02:00
66494c6760 Fix tests
All checks were successful
Publish / lints (push) Successful in 25s
/ Misc Linters (push) Successful in 28s
/ Build App (push) Successful in 1m3s
Publish / tests (push) Successful in 50s
Publish / Publishing (push) Successful in 1m7s
2024-08-16 16:39:44 +02:00
05320916b2 0.0.4
Some checks failed
/ Misc Linters (push) Successful in 24s
/ Build App (push) Failing after 29s
Publish / lints (push) Successful in 22s
Publish / tests (push) Failing after 27s
Publish / Publishing (push) Has been skipped
2024-08-16 16:37:24 +02:00
e6857f5b5d 0.0.2 2024-08-16 16:36:51 +02:00
5dba5471d1 Reject illegal names 2024-08-16 16:34:43 +02:00
f1ff9f1c38 Update flake.lock 2024-08-16 16:34:26 +02:00
7386a29ec5 flake.lock: Update
Some checks failed
/ Misc Linters (push) Successful in 25s
/ Build App (push) Failing after 24s
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/693bc46d169f5af9c992095736e82c3488bf7dbb' (2024-07-14)
  → 'github:nixos/nixpkgs/c3aa7b8938b17aebd2deecf7be0636000d62a2b9' (2024-08-14)
2024-08-16 15:53:10 +02:00
88b158b7b4 Lint before publish
All checks were successful
/ Misc Linters (push) Successful in 27s
/ Build App (push) Successful in 59s
2024-08-16 15:10:18 +02:00
a2e78e2e36 Fallback for missing environment variable
All checks were successful
/ Misc Linters (push) Successful in 20s
/ Build App (push) Successful in 50s
Publish / tests (push) Successful in 51s
Publish / Publishing (push) Successful in 1m57s
2024-08-16 15:06:41 +02:00
b2bab79562 Run shellcheck and hadolint in CI
Some checks failed
/ Build App (push) Failing after 28s
/ Misc Linters (push) Successful in 35s
2024-08-16 14:45:13 +02:00
0b8bddb1f6 Run shellcheck in CI
Some checks failed
/ Check scripts (push) Failing after 22s
/ Build App (push) Failing after 41s
2024-08-16 14:41:51 +02:00
cbd03de6a3 Load upload dir from ENV 2024-08-16 14:41:33 +02:00
0842c107e6 Merge pull request 'chore(deps): update dependency vite to v5.4.1' (#47) from renovate/vite-5.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 1m15s
Reviewed-on: #47
2024-08-15 19:46:33 +02:00
a59f8688d0 chore(deps): update dependency vite to v5.4.1
All checks were successful
/ Build App (pull_request) Successful in 3m43s
2024-08-15 16:02:40 +00:00
91bba73796 Merge pull request 'chore(deps): lock file maintenance' (#46) from renovate/lock-file-maintenance into main
All checks were successful
/ Build App (push) Successful in 1m1s
Reviewed-on: #46
2024-08-13 14:57:39 +02:00
85e1874188 chore(deps): lock file maintenance
All checks were successful
/ Build App (pull_request) Successful in 1m0s
2024-08-13 14:56:31 +02:00
6ab6560009 Merge pull request 'chore(deps): update dependency @sveltejs/adapter-auto to v3.2.4' (#42) from renovate/sveltejs-adapter-auto-3.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 57s
Reviewed-on: #42
2024-08-13 14:56:21 +02:00
16bbb29f32 chore(deps): update dependency @sveltejs/adapter-auto to v3.2.4
All checks were successful
/ Build App (pull_request) Successful in 54s
2024-08-13 14:49:58 +02:00
c0c35b8e0f Merge pull request 'chore(deps): update dependency @sveltejs/adapter-node to v5.2.2' (#43) from renovate/sveltejs-adapter-node-5.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 55s
Reviewed-on: #43
2024-08-13 14:49:48 +02:00
626982fe3e chore(deps): update dependency @sveltejs/adapter-node to v5.2.2
All checks were successful
/ Build App (pull_request) Successful in 53s
2024-08-13 10:44:54 +02:00
79e079f115 Merge pull request 'chore(deps): update dependency @sveltejs/kit to v2.5.22' (#44) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 55s
Reviewed-on: #44
2024-08-13 10:44:45 +02:00
cd933b77cb chore(deps): update dependency @sveltejs/kit to v2.5.22
All checks were successful
/ Build App (pull_request) Successful in 54s
2024-08-13 10:41:55 +02:00
ef4ddf872f Merge pull request 'chore(deps): update dependency typescript-eslint to v8.1.0' (#45) from renovate/typescript-eslint-monorepo into main
All checks were successful
/ Build App (push) Successful in 1m18s
Reviewed-on: #45
2024-08-13 10:41:44 +02:00
ca3178e42a chore(deps): update dependency typescript-eslint to v8.1.0
All checks were successful
/ Build App (pull_request) Successful in 1m31s
2024-08-12 22:03:35 +00:00
338d3ccdbb Merge pull request 'chore(deps): lock file maintenance' (#35) from renovate/lock-file-maintenance into main
All checks were successful
/ Build App (push) Successful in 1m6s
Reviewed-on: #35
2024-08-10 11:52:41 +02:00
83eb10aadd chore(deps): lock file maintenance
All checks were successful
/ Build App (pull_request) Successful in 2m2s
2024-08-10 09:50:31 +00:00
2858c8c375 Merge pull request 'chore(deps): update dependency typescript-eslint to v8.0.1' (#34) from renovate/typescript-eslint-monorepo into main
All checks were successful
/ Build App (push) Successful in 55s
Reviewed-on: #34
2024-08-10 11:33:03 +02:00
0c09a354b8 chore(deps): update dependency typescript-eslint to v8.0.1
All checks were successful
/ Build App (pull_request) Successful in 59s
2024-08-10 11:28:38 +02:00
52ada9d040 Merge pull request 'chore(deps): update dependency vite to v5.4.0' (#36) from renovate/vite-5.x-lockfile into main
Some checks are pending
/ Build App (push) Waiting to run
Reviewed-on: #36
2024-08-10 11:28:14 +02:00
6cf7361905 chore(deps): update dependency vite to v5.4.0
Some checks failed
/ Build App (pull_request) Has been cancelled
2024-08-10 11:28:09 +02:00
335f341839 Merge pull request 'chore(deps): update dependency @sveltejs/adapter-auto to v3.2.3' (#37) from renovate/sveltejs-adapter-auto-3.x-lockfile into main
Some checks are pending
/ Build App (push) Waiting to run
Reviewed-on: #37
2024-08-10 11:27:49 +02:00
f4d5ccd4c3 chore(deps): update dependency @sveltejs/adapter-auto to v3.2.3
Some checks failed
/ Build App (pull_request) Has been cancelled
2024-08-10 11:27:35 +02:00
abd6a8a3be Merge pull request 'chore(deps): update dependency @sveltejs/adapter-node to v5.2.1' (#38) from renovate/sveltejs-adapter-node-5.x-lockfile into main
Some checks are pending
/ Build App (push) Waiting to run
Reviewed-on: #38
2024-08-10 11:27:22 +02:00
990e0e131f chore(deps): update dependency @sveltejs/adapter-node to v5.2.1
All checks were successful
/ Build App (pull_request) Successful in 1m5s
2024-08-10 11:24:27 +02:00
d6c24702d6 Merge pull request 'chore(deps): update dependency @sveltejs/kit to v2.5.21' (#39) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 1m31s
Reviewed-on: #39
2024-08-10 11:24:19 +02:00
13467c1c8f chore(deps): update dependency @sveltejs/kit to v2.5.21
All checks were successful
/ Build App (pull_request) Successful in 59s
2024-08-10 11:22:57 +02:00
3b338ea854 Merge pull request 'chore(deps): update dependency @types/node to v20.14.15' (#40) from renovate/node-20.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 58s
Reviewed-on: #40
2024-08-10 11:22:49 +02:00
7a85f1aae3 chore(deps): update dependency @types/node to v20.14.15
All checks were successful
/ Build App (pull_request) Successful in 53s
2024-08-10 11:21:37 +02:00
1e90cf7fc5 Merge pull request 'chore(deps): update dependency eslint to v9.9.0' (#41) from renovate/eslint-monorepo into main
All checks were successful
/ Build App (push) Successful in 56s
Reviewed-on: #41
2024-08-10 11:21:29 +02:00
1b36f81b85 chore(deps): update dependency eslint to v9.9.0
All checks were successful
/ Build App (pull_request) Successful in 51s
2024-08-10 01:02:12 +00:00
fba4548df4 Merge pull request 'chore(deps): lock file maintenance' (#32) from renovate/lock-file-maintenance into main
All checks were successful
/ Build App (push) Successful in 55s
Reviewed-on: #32
2024-08-04 15:57:28 +02:00
8ca2e03e6b chore(deps): lock file maintenance
All checks were successful
/ Build App (pull_request) Successful in 1m1s
2024-08-04 13:50:26 +00:00
ac6c82f2d8 Merge pull request 'chore(deps): update dependency @types/node to v20.14.14' (#33) from renovate/node-20.x-lockfile into main
All checks were successful
/ Build App (push) Successful in 57s
Reviewed-on: #33
2024-08-04 15:37:17 +02:00
35dc5db5b9 Merge pull request 'chore(deps): update dependency globals to v15.9.0' (#31) from renovate/globals-15.x-lockfile into main
Some checks are pending
/ Build App (push) Waiting to run
Reviewed-on: #31
2024-08-04 15:36:58 +02:00
186d6423d2 Merge pull request 'chore(deps): update dependency @sveltejs/kit to v2.5.20' (#30) from renovate/sveltejs-kit-2.x-lockfile into main
Some checks are pending
/ Build App (push) Waiting to run
Reviewed-on: #30
2024-08-04 15:36:03 +02:00
9b7cd22d31 chore(deps): update dependency @sveltejs/kit to v2.5.20
All checks were successful
/ Build App (pull_request) Successful in 55s
2024-08-02 19:01:42 +00:00
4e7c757e71 chore(deps): update dependency @types/node to v20.14.14
All checks were successful
/ Build App (pull_request) Successful in 1m4s
2024-08-02 10:01:55 +00:00
c7ce50b334 chore(deps): update dependency globals to v15.9.0
All checks were successful
/ Build App (pull_request) Successful in 57s
2024-08-01 13:01:42 +00:00
518041ce4e Merge pull request 'chore(deps): lock file maintenance' (#29) from renovate/lock-file-maintenance into main
All checks were successful
/ Build App (push) Successful in 1m3s
Reviewed-on: #29
2024-08-01 11:13:50 +02:00
2ca056059d chore(deps): lock file maintenance
All checks were successful
/ Build App (pull_request) Successful in 55s
2024-08-01 09:08:49 +00:00
66e2cce28d Merge pull request 'chore(deps): update dependency vitest to v2.0.5' (#27) from renovate/vitest-monorepo into main
All checks were successful
/ Build App (push) Successful in 48s
Reviewed-on: #27
2024-08-01 11:07:11 +02:00
deb3382023 chore(deps): update dependency vitest to v2.0.5
All checks were successful
/ Build App (pull_request) Successful in 50s
2024-08-01 11:06:07 +02:00
667eaabd58 Merge pull request 'chore(deps): update dependency typescript-eslint to v8.0.0' (#28) from renovate/typescript-eslint-monorepo into main
All checks were successful
/ Build App (push) Successful in 51s
Reviewed-on: #28
2024-08-01 11:05:54 +02:00
13c958db03 chore(deps): update dependency typescript-eslint to v8.0.0
All checks were successful
/ Build App (pull_request) Successful in 57s
2024-07-31 19:01:18 +00:00
36a297837d chore(deps): update dependency svelte-check to v3.8.5 (#25)
All checks were successful
/ Build App (push) Successful in 1m1s
2024-07-31 00:05:07 +02:00
07fcf86fef chore(deps): update dependency typescript-eslint to v8.0.0-alpha.60 (#26)
Some checks are pending
/ Build App (push) Waiting to run
2024-07-31 00:03:41 +02:00
ce25ef7ec1 chore(deps): update dependency typescript-eslint to v8.0.0-alpha.60
All checks were successful
/ Build App (pull_request) Successful in 1m52s
2024-07-30 22:01:52 +00:00
84e8d3b2bd chore(deps): update dependency svelte-check to v3.8.5
All checks were successful
/ Build App (pull_request) Successful in 52s
2024-07-30 19:01:06 +00:00
9c8459c3e1 CI step naming
All checks were successful
/ Build App (push) Successful in 1m1s
2024-07-30 19:24:58 +02:00
26 changed files with 821 additions and 287 deletions

View File

@ -0,0 +1,24 @@
on:
workflow_call:
push:
branches:
- main
pull_request:
schedule:
- cron: '0 0 * * *'
jobs:
shellcheck:
name: Misc Linters
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: https://gitea.com/actions/checkout@v4
- name: Lint shellscripts
uses: ludeeus/action-shellcheck@master
- name: Lint Dockerfile
uses: hadolint/hadolint-action@v3.1.0
with:
dockerfile: ./Containerfile

View File

@ -6,6 +6,9 @@ on:
- 'v*'
jobs:
lints:
uses: ./.gitea/workflows/lints.yml
tests:
uses: ./.gitea/workflows/node.yml
@ -21,7 +24,7 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v4
- name: Login to DockerHub
- name: Login to Container Registry
uses: docker/login-action@v3
with:
registry: https://git.vbrandl.net

7
.gitignore vendored
View File

@ -20,5 +20,12 @@ Thumbs.db
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth
uploads
.direnv

View File

@ -1,7 +1,7 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"trailingComma": "es5",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]

View File

@ -1,4 +1,4 @@
FROM node:21-alpine AS builder
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
@ -7,9 +7,12 @@ RUN npm ci
COPY . .
RUN npm run build
FROM node:21-alpine
USER node:node
FROM node:22-alpine
WORKDIR /app
RUN chown -R node:node /app
USER node:node
COPY ./container/entrypoint.sh /entrypoint.sh
COPY package.json .
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/build ./build
CMD ["node", "./build/index.js"]
ENTRYPOINT ["sh", "/entrypoint.sh"]

12
container/entrypoint.sh Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env sh
set -e
export STORAGE_PATH="${STORAGE_PATH:-./uploads}"
entrypoint() {
mkdir -p "${STORAGE_PATH}"
node ./build/index.js
}
entrypoint

View File

@ -15,19 +15,19 @@ export default [
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
...globals.node,
},
},
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser
}
}
parser: ts.parser,
},
},
},
{
ignores: ['build/', '.svelte-kit/', 'dist/']
}
ignores: ['build/', '.svelte-kit/', 'dist/'],
},
];

6
flake.lock generated
View File

@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1720957393,
"narHash": "sha256-oedh2RwpjEa+TNxhg5Je9Ch6d3W1NKi7DbRO1ziHemA=",
"lastModified": 1723637854,
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "693bc46d169f5af9c992095736e82c3488bf7dbb",
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
"type": "github"
},
"original": {

703
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,29 @@
{
"name": "fotochallenge",
"version": "0.0.1",
"version": "0.0.7",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
"format": "prettier --write .",
"test:integration": "playwright test",
"test:unit": "vitest"
},
"devDependencies": {
"@playwright/test": "^1.46.1",
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.2.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/bunyan": "^1.8.11",
"@types/eslint": "^9.0.0",
"@types/node": "^20.14.11",
"@types/uuid": "^10.0.0",
"bulma": "^1.0.1",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
@ -39,5 +44,9 @@
"type": "module",
"engines": {
"node": ">20"
},
"dependencies": {
"bunyan": "^1.8.15",
"uuid": "^10.0.0"
}
}

12
playwright.config.ts Normal file
View File

@ -0,0 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173,
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
};
export default config;

View File

@ -1,4 +1,4 @@
{
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: ['local>renovate-bot/renovate-config']
extends: ['local>renovate-bot/renovate-config'],
}

6
src/app.d.ts vendored
View File

@ -9,3 +9,9 @@ declare global {
// interface Platform {}
}
}
declare namespace App {
interface Locals {
requestId: string;
}
}

View File

@ -4,6 +4,7 @@
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gabi und Hannes Fotochallenge</title>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">

43
src/hooks.server.ts Normal file
View File

@ -0,0 +1,43 @@
import { log, timedExecution, requestIdHeader } from '$lib';
import { validate, v7 as uuidv7 } from 'uuid';
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// use incoming requestId, if it is a valid uuid, else generate one
const reqIdFromRequest = event.request.headers.get(requestIdHeader);
const { requestId, fromRequest } =
reqIdFromRequest && validate(reqIdFromRequest)
? { requestId: reqIdFromRequest, fromRequest: true }
: { requestId: uuidv7(), fromRequest: false };
const context = {
requestId,
route: event.route.id,
method: event.request.method,
userAgent: event.request.headers.get('user-agent'),
clientIP: event.getClientAddress(),
};
if (fromRequest) {
log.trace(context, 'using incoming request-id');
}
// make requestId available to handlers
event.locals.requestId = requestId;
const { executionTime, result: response } = await timedExecution(async () => {
return await resolve(event);
});
response.headers.set(requestIdHeader, requestId);
log.info(
{
executionTime: `${executionTime}ms`,
status: response.status,
size: response.headers.get('content-length'),
...context,
},
'finished request'
);
return response;
};

View File

@ -2,7 +2,31 @@ import safePath from '$lib';
import { describe, it, expect } from 'vitest';
describe('safe path', () => {
it('removes non alphanum from string', () => {
expect(safePath('../../!=-.,/abc123')).toBe('abc123');
it('reject names with ../', () => {
expect(safePath('./uplodas', '../foobar')).toBe(false);
});
it('accept names with ./', () => {
expect(safePath('./uplodas', './foobar')).toBe(true);
});
it('reject names with /', () => {
expect(safePath('./uplodas', 'foo/bar')).toBe(false);
});
it('accept happy path', () => {
expect(safePath('./uplodas', 'foobar')).toBe(true);
});
it('accept names starting with `..`', () => {
expect(safePath('./uplodas', '..foobar')).toBe(true);
});
it('accept names ending with `..`', () => {
expect(safePath('./uplodas', 'foobar..')).toBe(true);
});
it('accept names starting and ending with `..`', () => {
expect(safePath('./uplodas', '..foobar..')).toBe(true);
});
});

View File

@ -1,6 +1,44 @@
// place files you want to import through the `$lib` alias in this folder.
import path from 'path';
import bunyan from 'bunyan';
import type { MaybePromise } from '@sveltejs/kit';
const safePath = (input: string) => input.replace(/\W/g, '');
export const storagePath: string = './uploads';
export const log = bunyan.createLogger({
name: 'fotochallenge',
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
src: true,
});
function safePath(basePath: string, name: string): boolean {
const fullPath = `${basePath}/${name}`;
const relative = path.relative(basePath, fullPath);
return (
!!relative &&
// does move out of `basePath`
!relative.startsWith(`..${path.sep}`) &&
// exactly one layer deep, e.g. no `./uplodas/foo/bar`
!relative.includes(path.sep) &&
// result is not an absolute path
!path.isAbsolute(relative)
);
}
const defaultPath: string = './uploads';
if (!('STORAGE_PATH' in process.env)) {
log.warn(`'STORAGE_PATH' environment variable is not set. Defaulting to ${defaultPath}`);
}
export const storagePath: string = process.env.STORAGE_PATH ?? defaultPath;
export const requestIdHeader = 'x-request-id';
export default safePath;
export async function timedExecution<T>(
fn: () => MaybePromise<T>
): Promise<{ executionTime: number; result: T }> {
const start = process.hrtime();
const result = await fn();
const end = process.hrtime(start);
const executionTime = (end[0] * 1e6 + end[1]) / 1e6;
return { executionTime, result };
}

View File

@ -1,7 +1,7 @@
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { fail } from '@sveltejs/kit';
import type { RequestEvent } from './$types';
import safePath, { storagePath } from '$lib';
import safePath, { storagePath, log } from '$lib';
import { hash } from 'crypto';
import path from 'path';
@ -12,29 +12,44 @@ const mkdirIfNotExists = (path: string) => {
};
export const actions = {
default: async ({ request }: RequestEvent) => {
default: async ({ request, locals }: RequestEvent) => {
let context: { [key: string]: string | string[] } = { requestId: locals.requestId };
const data = await request.formData();
const formFiles = data.getAll('files');
if (!formFiles) {
log.debug(context, 'missing files');
return fail(400, { field: 'files', files: formFiles, missing: true });
} else if (!(formFiles as File[])) {
log.debug(context, 'invalid files');
return fail(400, { field: 'files', files: formFiles, incorrect: true });
}
const files = formFiles as File[];
console.log(files);
const fileNames = files.map((file) => file.name);
context = { fileNames, ...context };
if (files.length === 0) {
log.debug(context, 'empty files');
return fail(400, { field: 'files', files: formFiles, empty: true });
}
const formName = data.get('name');
if (!formName) {
log.debug(context, 'missing name');
return fail(400, { field: 'name', name: formName, missing: true });
} else if (!(formName as string)) {
log.debug(context, 'invalid name');
return fail(400, { field: 'name', name: formName, incorrect: true });
}
const name = safePath(formName as string);
const name = formName as string;
context = { name, ...context };
if (!safePath(storagePath, name)) {
log.warn(context, 'Supplied name would cause dir traversal. Rejecting...');
return fail(400, { field: 'name', name: name, incorrect: true });
}
log.info(context, 'Uploading files');
files.forEach(async (file) => {
const outPath = `${storagePath}/${name}`;
@ -42,17 +57,19 @@ export const actions = {
const ext = path.extname(file.name);
mkdirIfNotExists(outPath);
const filename = hash('sha1', content);
const fullPath = `${outPath}/${filename}${ext}`;
const filename = `${hash('sha1', content)}${ext}`;
const fullPath = `${outPath}/${filename}`;
context = { file: fullPath, ...context };
if (existsSync(fullPath)) {
console.warn(`${fullPath} has already been uploaded. Skipping...`);
log.debug(context, 'File has already been uploaded. Skipping...');
} else {
log.debug(context, 'saving file');
writeFileSync(fullPath, Buffer.from(await file.arrayBuffer()), { flag: 'a+' });
}
});
return {
success: true
success: true,
};
}
},
};

View File

@ -13,7 +13,7 @@
const siPrefixes = new Map([
[1_000_000, 'M'],
[1_000, 'k']
[1_000, 'k'],
]);
const fileSize = (files: FileList) => {
const size = Array.from(files)

View File

@ -0,0 +1,50 @@
import { storagePath } from '$lib';
import { json } from '@sveltejs/kit';
import { stat, access, constants } from 'fs/promises';
import type { RequestHandler } from './$types';
type Status = 'OK' | 'ERROR';
type Result = { status: Status; checks: Checks };
type Checks = { [key: string]: CheckResult };
type CheckResult = true | string;
const fileExists = async (path: string) => !!(await stat(path).catch(() => false));
const isDirectory = async (path: string) =>
!!(await stat(path)
.then((s) => s.isDirectory())
.catch(() => false));
const isWritable = async (path: string) =>
await access(path, constants.W_OK)
.then(() => true)
.catch(() => false);
const checkStoragePath = async (path: string): Promise<CheckResult> => {
if (!fileExists(path)) {
return '`STORAGE_PATH` does not exist';
} else if (!isDirectory(path)) {
return '`STORAGE_PATH` is not a directory';
} else if (!isWritable(path)) {
return '`STORAGE_PATH` is not writable';
}
return true;
};
export const GET: RequestHandler = async () => {
const storagePathResult = await checkStoragePath(storagePath);
const checks: Checks = {
storagePath: storagePathResult,
};
const healthy = Object.values(checks)
.map((r) => (r === true ? true : false))
.reduce((prev, next) => prev && next, true);
const status = healthy ? 'OK' : 'ERROR';
const result: Result = { status, checks };
const httpStatus = healthy ? 200 : 500;
return json(result, { status: httpStatus, headers: { healthy: healthy.toString() } });
};

View File

@ -1,12 +1,12 @@
import { storagePath } from '$lib';
import { json } from '@sveltejs/kit';
import { readdirSync, statSync } from 'fs';
import type { RequestHandler } from './$types';
export function GET() {
const names = readdirSync(storagePath).filter((f) =>
statSync(`${storagePath}/${f}`).isDirectory()
);
names.sort();
export const GET: RequestHandler = () => {
const names = readdirSync(storagePath)
.filter((f) => statSync(`${storagePath}/${f}`).isDirectory())
.toSorted();
return json(names);
}
};

View File

@ -11,8 +11,8 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter()
}
adapter: adapter(),
},
};
export default config;

13
tests/health.test.ts Normal file
View File

@ -0,0 +1,13 @@
import { expect, test } from '@playwright/test';
test('healthy test', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
await expect(response.status()).toBe(200);
await expect(response.headers()).toHaveProperty('healthy');
await expect(response.headers()['healthy']).toBe('true');
const body = await response.json();
await expect(body).toHaveProperty('status');
await expect(body['status']).toBe('OK');
});

10
tests/page.test.ts Normal file
View File

@ -0,0 +1,10 @@
import { expect, test } from '@playwright/test';
test('contains header text', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('');
await expect(response.status()).toBe(200);
const body = (await response.body()).toString();
await expect(body).toContain('Gabi und Hannes Fotochallenge');
});

37
tests/requestid.test.ts Normal file
View File

@ -0,0 +1,37 @@
import { expect, test } from '@playwright/test';
import { validate, NIL } from 'uuid';
import { requestIdHeader } from '$lib';
test('response contains request-id header', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
const headers = response.headers();
await expect(headers).toHaveProperty(requestIdHeader);
});
test('request-id is valid uuid', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(validate(requestId)).toBe(true);
});
test('reuse valid incoming uuid', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health', { headers: { [requestIdHeader]: NIL } });
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(requestId).toBe(NIL);
});
test('ignore invalid incoming uuid', async ({ playwright }) => {
const invalid = '00000000-0000-0000-0000-00000000000z';
const context = await playwright.request.newContext();
const response = await context.get('health', { headers: { [requestIdHeader]: invalid } });
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(requestId).not.toBe(invalid);
});

View File

@ -5,14 +5,14 @@ export default defineConfig({
plugins: [sveltekit()],
test: {
include: ['src/**/*.{test,spec}.{js,ts}']
include: ['src/**/*.{test,spec}.{js,ts}'],
},
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "src/variables.scss" as *;'
}
}
}
additionalData: '@use "src/variables.scss" as *;',
},
},
},
});