Compare commits

..

974 Commits

Author SHA1 Message Date
Jonas Plum
a4a4baf88a fix: downgrade goreleaser-cross docker version (#1164) 2025-10-26 07:50:27 +00:00
Jonas Plum
148f625b00 fix: pin goreleaser-cross docker version (#1163) 2025-10-26 08:32:51 +01:00
Rumburaq2
97ebe9f01a feat: add image preview feature (#1161)
Co-authored-by: Jonas Plum <git@cugu.eu>
2025-10-26 07:38:09 +01:00
dependabot[bot]
770390c4ea build(deps): bump github.com/wneessen/go-mail from 0.6.2 to 0.7.1 (#1159)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 19:37:20 +02:00
Jonas Plum
4e03a52b71 fix: text alignment (#1158) 2025-09-21 16:58:46 +00:00
Jonas Plum
e475b38ea4 refactor: sanitize webhook auth payload (#1157) 2025-09-21 17:26:18 +02:00
Jonas Plum
e07afd0f3a chore: remove admin interface link from dashboard cards (#1156) 2025-09-21 17:25:31 +02:00
Jonas Plum
fedda9daaf chore: add more screenshots (#1155) 2025-09-21 14:50:28 +02:00
Jonas Plum
4d844c567c fix: multiple minor fixes (#1154) 2025-09-21 12:08:28 +00:00
Jonas Plum
9da90e7cc8 refactor: add root store (#1153) 2025-09-21 09:47:29 +00:00
Jonas Plum
d9f759c879 fix: ui basePath (#1152) 2025-09-21 09:02:00 +00:00
dependabot[bot]
1e3f2f24dc build(deps): bump github.com/go-chi/chi/v5 from 5.2.1 to 5.2.2 (#1149)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 07:02:08 +02:00
Jonas Plum
3cb097126c fix: rename binary (#1150) 2025-09-04 04:52:55 +00:00
Jonas Plum
df96362c3c fix: release docker ids (#1148) 2025-09-02 23:58:54 +00:00
Jonas Plum
377d2dad5f fix: cross-compile (#1147) 2025-09-02 23:27:23 +00:00
Jonas Plum
87fc0e6567 fix: working directory (#1146) 2025-09-02 22:02:52 +00:00
Jonas Plum
06fdae4ab9 fix: adapt goreleaser for cross compilation (#1145) 2025-09-02 21:43:33 +00:00
Jonas Plum
27129f24d5 fix: recreate http flag (#1144) 2025-09-02 20:54:47 +00:00
Jonas Plum
de105f19c1 fix: release CI (#1143) 2025-09-02 20:23:30 +00:00
Jonas Plum
eba2615ec0 refactor: remove pocketbase (#1138) 2025-09-02 21:58:08 +02:00
dependabot[bot]
f28c238135 build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.1 to 4.5.2 (#1132)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-31 06:39:18 +02:00
Jonas Plum
7de89a752c test: add upgrade tests (#1126) 2025-02-02 13:40:33 +01:00
Jonas Plum
b31f90c3ea feat: support integer custom field (#1123) 2025-01-02 23:37:26 +01:00
dependabot[bot]
87175f80a2 build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#1119)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-21 21:11:32 +01:00
dependabot[bot]
9a8125635b build(deps): bump golang.org/x/crypto from 0.24.0 to 0.31.0 (#1118)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-21 06:28:06 +01:00
Jonas Plum
86f4aa1d28 fix: relative day display (#1117) 2024-11-29 22:28:07 +00:00
Jonas Plum
7b92d59dff fix: relative day display (#1116) 2024-11-29 23:11:11 +01:00
Jonas Plum
6a8c92f1f6 fix: server setup (#1115) 2024-11-08 21:56:32 +01:00
Jonas Plum
9285aec468 fix: docker entrypoint permissions (#1114) 2024-11-06 02:12:09 +01:00
Jonas Plum
97d0cd3428 fix: goreleaser docker (#1113) 2024-11-06 02:02:47 +01:00
Jonas Plum
baba5b7a45 feat: docker entrypoint with environment variables (#1112) 2024-11-06 01:52:48 +01:00
Jonas Plum
d1cf75ab79 refactor: subcommands (#1111) 2024-11-06 01:21:31 +01:00
Jonas Plum
38a89f2c94 fix: docker latest image (#1110) 2024-11-04 23:08:28 +01:00
Jonas Plum
8c36ea5243 feat: scheduler example (#1109) 2024-11-04 23:07:17 +01:00
Jonas Plum
a2bdeecb0d feat: scheduler (#1108) 2024-11-04 22:30:20 +01:00
Jonas Plum
42797509f7 fix: set-app-url (#1107) 2024-11-04 20:50:18 +00:00
Jonas Plum
70ba16a6bd feat: docker healthcheck (#1106) 2024-11-04 20:47:55 +00:00
Jonas Plum
f42de34780 fix: ci docker tags 2024-09-30 03:55:39 +02:00
Jonas Plum
88f56a2bdb fix: ci docker login 2024-09-30 03:45:24 +02:00
Jonas Plum
88cc02b350 fix: goreleaser ci permissions (#1105) 2024-09-30 03:30:29 +02:00
Jonas Plum
46f7815699 feat: docker files (#1104) 2024-09-30 03:20:26 +02:00
Jonas Plum
ea03a3ed23 fix: prevent view update (#1102) 2024-09-20 00:02:15 +02:00
Jonas Plum
6346140de5 fix: multiple hooks (#1101) 2024-09-19 23:23:45 +02:00
Jonas Plum
d7bdf1d276 fix: curl example (#1099) 2024-08-13 08:09:46 +02:00
Jonas Plum
1e1022ab15 fix: reaction names (#1098) 2024-08-13 07:44:06 +02:00
Jonas Plum
a2dd6c05e6 feat: mobile ui (#1096) 2024-08-07 22:18:59 +02:00
Jonas Plum
96b7a9604c fix: multi select state handling (#1094) 2024-08-05 15:22:01 +02:00
Jonas Plum
21f1c3d328 feat: reset password (#1092) 2024-08-03 16:26:09 +02:00
Jonas Plum
84ae933cfb perf: search (#1091) 2024-08-03 14:58:55 +02:00
Jonas Plum
b929100d30 feat: enum custom field (#1090) 2024-08-03 13:43:41 +02:00
Jonas Plum
aba3dfaaa4 docs: add reaction docs (#1086) 2024-07-21 10:23:25 +02:00
Jonas Plum
c491f4e810 fix: search bar (#1088) 2024-07-21 10:09:55 +02:00
Jonas Plum
4db718660a feat: provide app url (#1087) 2024-07-21 09:24:06 +02:00
Jonas Plum
83251af565 fix: python PATH order (#1085) 2024-07-21 04:21:32 +02:00
Jonas Plum
a9e885598c feat: demo flags (#1084) 2024-07-21 04:07:23 +02:00
Jonas Plum
91429effe2 feat: improve python actions (#1083) 2024-07-21 02:56:43 +02:00
Jonas Plum
81bfbb2072 fix: redirect to login (#1082) 2024-07-20 06:51:14 +00:00
Jonas Plum
e9583a29fa test: reactions (#1081) 2024-07-20 07:50:42 +02:00
Jonas Plum
e2c8f1d223 feat: add reactions (#1074) 2024-07-20 06:39:02 +02:00
Jonas Plum
82ad50d228 refactor: create TicketDeleteDialog (#1079) 2024-07-12 21:09:48 +02:00
Jonas Plum
00b7ab585c chore: extract handleError function (#1078) 2024-07-12 18:52:00 +00:00
dependabot[bot]
a700791f43 build(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#1077)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 09:22:11 +02:00
dependabot[bot]
1106533892 build(deps): bump golang.org/x/image from 0.15.0 to 0.18.0 (#1072)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonas Plum <cugu@users.noreply.github.com>
2024-07-07 23:33:06 +00:00
dependabot[bot]
484beacead build(deps): bump github.com/pocketbase/pocketbase from 0.22.10 to 0.22.14 (#1073)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonas Plum <cugu@users.noreply.github.com>
2024-07-07 23:29:36 +00:00
Jonas Plum
1bf41747c6 fix: pr lint scopes (#1076) 2024-07-08 01:28:06 +02:00
Jonas Plum
ddaf4fe491 fix: goreleaser ci (#1071) 2024-07-08 00:40:29 +02:00
Jonas Plum
619c5c65ce refactor: improve setup and maintainability (#1067) 2024-07-08 00:16:37 +02:00
Jonas Plum
f5fcee0096 Enforce semantic PR titles (#1070) 2024-07-08 00:04:37 +02:00
dependabot[bot]
451f200612 Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#1058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 22:03:56 +01:00
dependabot[bot]
639b5a8b1e Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 (#1056)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-20 22:03:48 +01:00
dependabot[bot]
81931383fd Bump golang.org/x/crypto from 0.14.0 to 0.17.0 (#1050)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 23:11:58 +01:00
dependabot[bot]
233b5451b7 Bump ip from 1.1.8 to 1.1.9 in /ui (#1055)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 23:11:43 +01:00
dependabot[bot]
ccba3f81c0 Bump github.com/containerd/containerd from 1.6.8 to 1.6.26 (#1051)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 23:11:36 +01:00
dependabot[bot]
82e68a9a44 Bump follow-redirects from 1.15.2 to 1.15.4 in /ui (#1053)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-23 23:11:27 +01:00
dependabot[bot]
99360cc387 Bump github.com/go-jose/go-jose/v3 from 3.0.0 to 3.0.1 (#1049)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-30 21:36:18 +01:00
dependabot[bot]
4af2ad7644 Bump browserify-sign from 4.2.1 to 4.2.2 in /ui (#1045)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-28 00:28:36 +02:00
missingscrews
d4e625a9c2 Fix: Upgrade docker base image version to provide shared object dependencies (#1044) 2023-10-27 16:19:33 +02:00
dependabot[bot]
e724ec8883 Bump google.golang.org/grpc from 1.57.0 to 1.57.1 (#1042)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-25 23:52:14 +02:00
dependabot[bot]
0e19b80dd5 Bump decode-uri-component from 0.2.0 to 0.2.2 in /ui (#1041)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 00:41:48 +02:00
dependabot[bot]
a7e031151b Bump word-wrap from 1.2.3 to 1.2.5 in /ui (#1037)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 00:33:37 +02:00
dependabot[bot]
752d06702e Bump @babel/traverse from 7.19.3 to 7.23.2 in /ui (#1038)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 00:32:52 +02:00
dependabot[bot]
a37ceec393 Bump semver from 5.7.1 to 5.7.2 in /ui (#1035)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-24 00:32:13 +02:00
Jonas Plum
f27e20f002 Delete .github/renovate.json 2023-10-24 00:25:46 +02:00
renovate[bot]
7951196c4d Update vue monorepo to v2.7.15 (#1032)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-23 20:54:00 +00:00
renovate[bot]
e00426a4ef Update actions/setup-node action to v4 (#1034)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-23 22:47:29 +02:00
renovate[bot]
2918d39771 Update module github.com/aws/aws-sdk-go to v1.46.2 (#1033)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-23 19:11:48 +00:00
renovate[bot]
e39578a841 Update module github.com/aws/aws-sdk-go to v1.46.1 (#1031)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 22:37:38 +00:00
renovate[bot]
4d4f87156b Update dependency eslint-plugin-jest to v27.4.3 (#1030)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 22:36:35 +00:00
renovate[bot]
39ec9d82f4 Update dependency @vue/compiler-sfc to v3.3.6 (#1029)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 14:03:33 +00:00
renovate[bot]
8f6c437be1 Update module github.com/coreos/go-oidc/v3 to v3.7.0 (#1028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 11:15:13 +00:00
renovate[bot]
efacd28d2b Update dependency core-js to v3.33.1 (#1027)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 08:01:30 +00:00
renovate[bot]
f3f9174949 Update dependency @vue/compiler-sfc to v3.3.5 (#1026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-20 04:14:15 +00:00
renovate[bot]
4b9e9fbe40 Update module github.com/aws/aws-sdk-go to v1.46.0 (#1014)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-19 23:21:45 +02:00
renovate[bot]
0ffa48849d Update actions/checkout action to v4 (#987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-19 13:52:34 +02:00
renovate[bot]
e349891102 Update dependency @mdi/font to v7.3.67 (#1016)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:32:09 +00:00
renovate[bot]
e7dede18de Update dependency @types/prismjs to v1.26.2 (#1010)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:31:05 +00:00
renovate[bot]
f0aa1792eb Update dependency @types/lodash to v4.14.200 (#1008)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:30:30 +00:00
renovate[bot]
67dd6a2ead Update dependency sass to v1.69.4 (#1019)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:17:08 +00:00
renovate[bot]
d246db506a Update dependency eslint-plugin-jest to v27.4.2 (#1018)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:16:32 +00:00
renovate[bot]
e64e024140 Update dependency core-js to v3.33.0 (#1017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:16:06 +00:00
renovate[bot]
9ad2c4c4dd Update dependency @koumoul/vjsf to v2.23.2 (#1004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:12:50 +00:00
renovate[bot]
7dce84a3b5 Update dependency axios to v1.5.1 (#1012)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:10:28 +00:00
renovate[bot]
7ca552a225 Update module github.com/alecthomas/kong to v0.8.1 (#1013)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:10:14 +00:00
renovate[bot]
84d337394e Update dependency @types/vue-markdown to v2.2.3 (#1011)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:09:43 +00:00
renovate[bot]
4449e2096b Update dependency @types/luxon to v3.3.3 (#1009)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:07:29 +00:00
renovate[bot]
a6cb83e8c7 Update dependency @types/jest to v29.5.6 (#1006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:05:18 +00:00
renovate[bot]
a78c355f3f Update golang.org/x/exp digest to 7918f67 (#1003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-18 23:04:10 +00:00
renovate[bot]
69981ab975 Update docker/metadata-action action to v5 (#1023)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-19 00:44:03 +02:00
Jonas Plum
e0a8e3e479 Uplift (#1025) 2023-10-19 00:41:37 +02:00
missingscrews
e41c50c9c2 Configurable S3 Region (#1007)
Co-authored-by: Jonas Plum <cugu@users.noreply.github.com>
2023-10-18 22:45:49 +02:00
Jonas Plum
494fee194b Create CODEOWNERS 2023-10-16 13:25:31 +02:00
renovate[bot]
a3a38990bc Update docker/build-push-action action to v5 (#1001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-09 23:45:55 +02:00
renovate[bot]
430584e5ed Update docker/login-action action to v3 (#1002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-09 23:45:47 +02:00
renovate[bot]
7b5acb19a4 Update module github.com/aws/aws-sdk-go to v1.45.7 2023-09-11 21:05:23 +00:00
renovate[bot]
a2341423d7 Update module github.com/aws/aws-sdk-go to v1.45.6 2023-09-08 23:39:12 +00:00
renovate[bot]
42ea4b1f31 Update module github.com/aws/aws-sdk-go to v1.45.5 2023-09-08 02:08:48 +00:00
renovate[bot]
17230ef232 Update dependency core-js to v3.32.2 2023-09-07 16:59:10 +00:00
renovate[bot]
b2bc5932d0 Update module github.com/aws/aws-sdk-go to v1.45.4 2023-09-07 01:15:16 +00:00
renovate[bot]
853577c5ba Update module github.com/tus/tusd to v1.13.0 2023-09-06 12:26:51 +00:00
renovate[bot]
0722369c7a Update module golang.org/x/oauth2 to v0.12.0 2023-09-06 10:18:13 +00:00
renovate[bot]
0620fd40bd Update module github.com/aws/aws-sdk-go to v1.45.3 2023-09-06 08:34:06 +00:00
renovate[bot]
0798c2e196 Update dependency @types/lodash to v4.14.198 2023-09-06 04:41:21 +00:00
renovate[bot]
d1566d0cd0 Update golang.org/x/exp digest to 9212866 2023-09-06 01:18:46 +00:00
renovate[bot]
778a27d3b9 Update dependency luxon to v3.4.3 2023-09-05 21:04:03 +00:00
renovate[bot]
a7a1564726 Update dependency @babel/eslint-parser to v7.22.15 2023-09-05 16:45:41 +00:00
renovate[bot]
43f11dc054 Update dependency @koumoul/vjsf to v2.22.0 2023-09-05 12:29:41 +00:00
renovate[bot]
89afb1aab2 Update module github.com/aws/aws-sdk-go to v1.45.2 2023-09-01 23:06:10 +00:00
renovate[bot]
f9d129eae3 Update module github.com/aws/aws-sdk-go to v1.45.1 2023-09-01 00:52:36 +00:00
renovate[bot]
e13ffb75be Update dependency @types/luxon to v3.3.2 2023-08-31 01:28:36 +00:00
renovate[bot]
dd647fee1e Update module github.com/aws/aws-sdk-go to v1.45.0 2023-08-30 22:32:36 +00:00
renovate[bot]
4f7491f360 Update dependency axios to v1.5.0 2023-08-30 01:55:55 +00:00
renovate[bot]
f54bb043de Update module github.com/aws/aws-sdk-go to v1.44.334 2023-08-29 21:20:31 +00:00
renovate[bot]
f5144c6771 Update dependency luxon to v3.4.2 2023-08-26 23:39:12 +00:00
renovate[bot]
6056e5eaba Update dependency @babel/eslint-parser to v7.22.11 2023-08-26 02:58:10 +00:00
renovate[bot]
9c94208aa9 Update module github.com/aws/aws-sdk-go to v1.44.332 2023-08-25 22:48:17 +00:00
renovate[bot]
cf680816c0 Update module github.com/aws/aws-sdk-go to v1.44.330 2023-08-23 22:19:30 +00:00
renovate[bot]
9d12b41175 Update module github.com/aws/aws-sdk-go to v1.44.329 2023-08-23 07:31:26 +00:00
renovate[bot]
53d9192258 Update dependency luxon to v3.4.1 2023-08-23 03:14:44 +00:00
renovate[bot]
1dc7901bdf Update dependency @types/jest to v29.5.4 2023-08-23 00:47:27 +00:00
renovate[bot]
ee7cd96aee Update module github.com/aws/aws-sdk-go to v1.44.328 2023-08-22 00:33:11 +00:00
renovate[bot]
d23c7e721a Update module github.com/google/uuid to v1.3.1 2023-08-21 23:10:04 +00:00
renovate[bot]
7b0eb2d186 Update dependency sass to v1.66.1 2023-08-19 04:56:17 +00:00
renovate[bot]
b468a4a4cb Update dependency core-js to v3.32.1 2023-08-19 01:55:15 +00:00
renovate[bot]
8e4fe069b4 Update module github.com/aws/aws-sdk-go to v1.44.327 2023-08-18 21:51:21 +00:00
renovate[bot]
e53f5c9d7f Update dependency sass to v1.66.0 2023-08-18 04:50:23 +00:00
renovate[bot]
31a2153446 Update module github.com/aws/aws-sdk-go to v1.44.326 2023-08-18 01:51:24 +00:00
renovate[bot]
8b24418b6e Update golang.org/x/exp digest to d852ddb 2023-08-17 21:06:47 +00:00
renovate[bot]
3c8cd48cab Update module github.com/aws/aws-sdk-go to v1.44.325 2023-08-16 21:34:08 +00:00
renovate[bot]
4a0d50d7cc Update module github.com/aws/aws-sdk-go to v1.44.324 2023-08-15 21:08:45 +00:00
renovate[bot]
aedfd06d4b Update module github.com/aws/aws-sdk-go to v1.44.323 2023-08-14 21:46:30 +00:00
renovate[bot]
8057aa5cc8 Update golang.org/x/exp digest to 89c5cff 2023-08-11 19:10:37 +00:00
renovate[bot]
ca1a5815c5 Update module github.com/aws/aws-sdk-go to v1.44.321 2023-08-10 21:29:14 +00:00
renovate[bot]
ef0ca9946c Update module github.com/tidwall/gjson to v1.16.0 2023-08-10 17:06:03 +00:00
renovate[bot]
7caf657cc2 Update module github.com/gobwas/ws to v1.3.0 2023-08-10 13:38:29 +00:00
renovate[bot]
6276a587a3 Update module github.com/aws/aws-sdk-go to v1.44.320 2023-08-10 09:07:26 +00:00
renovate[bot]
793e68e802 Update golang.org/x/exp digest to 352e893 2023-08-10 08:20:59 +00:00
renovate[bot]
f6923f9b25 Update dependency sass to v1.65.1 2023-08-10 04:43:14 +00:00
renovate[bot]
3b34a5c65e Update dependency @types/lodash to v4.14.197 2023-08-10 01:56:30 +00:00
renovate[bot]
a2e9d6c36f Update dependency luxon to v3.4.0 2023-08-09 23:25:16 +00:00
renovate[bot]
dec44b6d1d Update golang.org/x/exp digest to 7b3493d 2023-08-09 20:46:31 +00:00
renovate[bot]
0b99764eb1 Update module github.com/aws/aws-sdk-go to v1.44.319 2023-08-09 16:23:04 +00:00
renovate[bot]
a317490f61 Update dependency vuetify to v2.7.1 2023-08-09 14:22:56 +00:00
renovate[bot]
55caa99e2f Update golang.org/x/exp digest to 050eac2 2023-08-08 03:11:43 +00:00
renovate[bot]
c927e5f338 Update module github.com/aws/aws-sdk-go to v1.44.318 2023-08-08 00:07:38 +00:00
renovate[bot]
6140b124bf Update dependency @babel/eslint-parser to v7.22.10 2023-08-07 21:39:58 +00:00
renovate[bot]
25b6895c9d Update dependency less to v4.2.0 2023-08-05 22:33:41 +00:00
renovate[bot]
8c2d666e5e Update module golang.org/x/oauth2 to v0.11.0 2023-08-05 07:46:19 +00:00
renovate[bot]
6aaabe9cbf Update module github.com/aws/aws-sdk-go to v1.44.317 2023-08-04 23:00:41 +00:00
renovate[bot]
a5f666a142 Update module github.com/aws/aws-sdk-go to v1.44.315 2023-08-02 22:13:59 +00:00
renovate[bot]
b4a1fcdfbe Update dependency json-schema-editor-vue to v2.2.3 2023-08-02 16:18:59 +00:00
renovate[bot]
657b2d4431 Update module github.com/aws/aws-sdk-go to v1.44.314 2023-08-01 21:44:00 +00:00
renovate[bot]
064abf7bb8 Update golang.org/x/exp digest to d63ba01 2023-08-01 16:47:53 +00:00
renovate[bot]
f7a80a841c Update dependency sass to v1.64.2 2023-08-01 07:14:47 +00:00
renovate[bot]
65cbf35611 Update module github.com/aws/aws-sdk-go to v1.44.313 2023-07-31 23:06:31 +00:00
renovate[bot]
4181439e71 Update module github.com/aws/aws-sdk-go to v1.44.312 2023-07-29 03:30:47 +00:00
renovate[bot]
39a9ebe778 Update golang.org/x/exp digest to b0cb94b 2023-07-29 00:09:54 +00:00
renovate[bot]
3edde1b7c7 Update module github.com/aws/aws-sdk-go to v1.44.311 2023-07-28 06:03:23 +00:00
renovate[bot]
fda16aca30 Update dependency core-js to v3.32.0 2023-07-28 03:37:12 +00:00
renovate[bot]
8d68f8fc6d Update module github.com/aws/aws-sdk-go to v1.44.310 2023-07-28 00:35:49 +00:00
renovate[bot]
826811034e Update module github.com/tidwall/gjson to v1.15.0 2023-07-27 18:15:47 +00:00
renovate[bot]
0c90b855a6 Update module github.com/aws/aws-sdk-go to v1.44.309 2023-07-26 21:02:01 +00:00
renovate[bot]
365649c709 Update dependency @types/lodash to v4.14.196 2023-07-26 03:17:05 +00:00
renovate[bot]
23d2aa2cf4 Update module github.com/aws/aws-sdk-go to v1.44.308 2023-07-26 01:21:45 +00:00
renovate[bot]
985975a2a1 Update module github.com/tus/tusd to v1.12.1 2023-07-25 22:00:19 +00:00
renovate[bot]
8a10d38d72 Update golang.org/x/exp digest to 515e97e 2023-07-25 14:35:49 +00:00
renovate[bot]
a79267f213 Update golang.org/x/exp digest to 302865e 2023-07-25 06:50:08 +00:00
renovate[bot]
b79a721fb7 Update module github.com/aws/aws-sdk-go to v1.44.307 2023-07-24 21:26:56 +00:00
renovate[bot]
ce8485f8d2 Update dependency sass to v1.64.1 2023-07-22 04:40:04 +00:00
renovate[bot]
a14baa49c6 Update module github.com/aws/aws-sdk-go to v1.44.306 2023-07-21 23:26:53 +00:00
renovate[bot]
c623859f64 Update dependency @vue/test-utils to v2.4.1 2023-07-21 08:10:43 +00:00
renovate[bot]
7bc401041c Update dependency @types/luxon to v3.3.1 2023-07-21 04:25:19 +00:00
renovate[bot]
1504ee4cf6 Update module github.com/aws/aws-sdk-go to v1.44.305 2023-07-20 21:10:15 +00:00
renovate[bot]
4b263b4e7b Update module github.com/aws/aws-sdk-go to v1.44.304 2023-07-20 09:42:37 +00:00
renovate[bot]
dda21ad4de Update dependency sass to v1.64.0 2023-07-20 03:23:00 +00:00
renovate[bot]
b54c8ab813 Update module github.com/aws/aws-sdk-go to v1.44.303 2023-07-19 22:38:13 +00:00
renovate[bot]
268647084d Update module github.com/aws/aws-sdk-go to v1.44.302 2023-07-19 02:08:37 +00:00
renovate[bot]
d6452271d1 Update module github.com/aws/aws-sdk-go to v1.44.301 2023-07-17 23:08:38 +00:00
renovate[bot]
4b48bc312b Update module github.com/go-chi/chi/v5 to v5.0.10 2023-07-14 06:28:03 +00:00
renovate[bot]
e3a30e173d Update dependency eslint-plugin-jest to v27.2.3 2023-07-14 03:26:42 +00:00
renovate[bot]
0d4d0cc596 Update golang.org/x/exp digest to 613f0c0 2023-07-14 00:15:16 +00:00
renovate[bot]
34cdfc6339 Update module github.com/aws/aws-sdk-go to v1.44.300 2023-07-13 21:20:25 +00:00
renovate[bot]
07ae578686 Update module github.com/blevesearch/bleve/v2 to v2.3.9 2023-07-13 08:10:57 +00:00
renovate[bot]
caf47c20ab Update module github.com/iancoleman/strcase to v0.3.0 2023-07-13 03:25:57 +00:00
renovate[bot]
37e2d4e299 Update dependency @babel/eslint-parser to v7.22.9 2023-07-12 22:23:25 +00:00
renovate[bot]
d41fdac3bf Update golang.org/x/exp digest to 06a737e 2023-07-11 18:27:34 +00:00
renovate[bot]
4960d1a28f Update module github.com/aws/aws-sdk-go to v1.44.299 2023-07-11 09:25:00 +00:00
renovate[bot]
e0e0b4108b Update golang.org/x/exp digest to fffb143 2023-07-11 06:51:09 +00:00
renovate[bot]
bc71eb8bc9 Update typescript-eslint monorepo to v5.62.0 2023-07-10 21:47:53 +00:00
renovate[bot]
2f665be433 Update dependency @types/jest to v29.5.3 2023-07-10 20:32:18 +00:00
renovate[bot]
701740d7c6 Update module github.com/aws/aws-sdk-go to v1.44.298 2023-07-07 21:54:32 +00:00
renovate[bot]
8720ab1485 Update dependency @babel/eslint-parser to v7.22.7 2023-07-07 00:47:57 +00:00
renovate[bot]
185c47c791 Update module github.com/aws/aws-sdk-go to v1.44.297 2023-07-06 22:55:32 +00:00
renovate[bot]
5a935b391c Update module golang.org/x/oauth2 to v0.10.0 2023-07-06 07:31:01 +00:00
renovate[bot]
680ac096f0 Update dependency core-js to v3.31.1 2023-07-06 04:57:35 +00:00
renovate[bot]
34bd77b2b2 Update module github.com/aws/aws-sdk-go to v1.44.296 2023-07-05 21:28:41 +00:00
renovate[bot]
a9184a149a Update dependency vuetify to v2.7.0 2023-07-05 19:02:59 +00:00
renovate[bot]
b4e9438420 Update dependency @babel/eslint-parser to v7.22.6 2023-07-04 10:55:47 +00:00
renovate[bot]
38f7d4639b Update module github.com/aws/aws-sdk-go to v1.44.295 2023-07-04 02:17:36 +00:00
renovate[bot]
b210d90d53 Update typescript-eslint monorepo to v5.61.0 2023-07-03 21:49:06 +00:00
renovate[bot]
c9c5e2ca1c Update module github.com/aws/aws-sdk-go to v1.44.294 2023-07-01 08:47:26 +00:00
renovate[bot]
543bbd4460 Update module github.com/aws/aws-sdk-go to v1.44.293 2023-06-30 00:51:27 +00:00
renovate[bot]
3784e8fc98 Update dependency typescript to v5.1.6 2023-06-29 03:19:30 +00:00
renovate[bot]
2cd5ca81c5 Update module github.com/aws/aws-sdk-go to v1.44.292 2023-06-28 22:17:01 +00:00
renovate[bot]
067d544c60 Update module github.com/tus/tusd to v1.11.0 2023-06-28 12:18:51 +02:00
renovate[bot]
54adc98e2c Update dependency @babel/eslint-parser to v7.22.5 2023-06-28 12:18:42 +02:00
renovate[bot]
beaf9c0088 Update typescript-eslint monorepo to v5.60.1 2023-06-28 09:56:01 +02:00
renovate[bot]
a3a7708da7 Update vue monorepo 2023-06-28 09:55:48 +02:00
renovate[bot]
6fdca18a32 Replace dependency babel-eslint with @babel/eslint-parser 7.11.0 2023-06-28 09:55:32 +02:00
renovate[bot]
2216823f51 Update nginx Docker tag to v1.25 2023-06-28 09:55:11 +02:00
renovate[bot]
c3b998cba4 Update module golang.org/x/oauth2 to v0.9.0 2023-06-28 09:54:39 +02:00
renovate[bot]
42f0ece838 Update module github.com/alecthomas/kong to v0.8.0 2023-06-28 09:54:28 +02:00
renovate[bot]
cce8320acf Update module github.com/arangodb/go-driver to v1.6.0 2023-06-28 06:05:03 +00:00
renovate[bot]
ec7263bc19 Update module github.com/alecthomas/kong-yaml to v0.2.0 2023-06-28 04:00:34 +00:00
renovate[bot]
bb5b01577f Update dependency typescript to v5.1.5 2023-06-28 02:23:37 +00:00
renovate[bot]
567e90d3b6 Update module github.com/aws/aws-sdk-go to v1.44.291 2023-06-27 23:22:07 +00:00
renovate[bot]
ad104c3d01 Update dependency sass to v1.63.6 2023-06-27 20:07:25 +00:00
renovate[bot]
c947365a56 Update dependency core-js to v3.31.0 2023-06-27 17:12:43 +00:00
renovate[bot]
f69363a35b Update dependency @vue/test-utils to v2.4.0 2023-06-27 12:35:04 +00:00
renovate[bot]
3d058d67ae Update module github.com/stretchr/testify to v1.8.4 2023-06-27 09:12:18 +00:00
renovate[bot]
1b8a9519b4 Update module github.com/imdario/mergo to v0.3.16 2023-06-27 06:43:04 +00:00
renovate[bot]
45589c1304 Update golang.org/x/exp digest to 97b1e66 2023-06-27 03:24:21 +00:00
renovate[bot]
2701a4b8fe Update module github.com/aws/aws-sdk-go to v1.44.290 2023-06-27 00:41:54 +00:00
renovate[bot]
10f9cf5246 Update dependency less-loader to v11.1.3 2023-06-26 21:53:20 +00:00
renovate[bot]
57e38f3545 Update dependency eslint-plugin-jest to v27.2.2 2023-06-26 19:21:08 +00:00
renovate[bot]
8b4aff0ce3 Update dependency d3 to v7.8.5 2023-06-26 17:19:50 +00:00
renovate[bot]
d21924133c Update dependency @types/lodash to v4.14.195 2023-06-26 14:22:41 +00:00
renovate[bot]
f59e18a09a Update dependency @types/jest to v29.5.2 2023-06-26 11:53:38 +00:00
renovate[bot]
ac994bfde7 Update dependency @testing-library/vue to v7 2023-06-26 08:51:07 +02:00
renovate[bot]
73cc97b3dc Update dependency typescript to v5 2023-06-26 08:50:55 +02:00
renovate[bot]
d07081f503 Update module github.com/gobwas/ws to v1.2.1 2023-05-25 07:55:23 +00:00
renovate[bot]
4843338d5e Update module github.com/coreos/go-oidc/v3 to v3.6.0 2023-05-25 04:43:59 +00:00
renovate[bot]
bca4667cb5 Update dependency sass to v1.62.1 2023-05-25 01:45:11 +00:00
renovate[bot]
2a6a77274a Update module github.com/aws/aws-sdk-go to v1.44.269 2023-05-24 22:52:13 +00:00
renovate[bot]
f992b6d3cb Update dependency core-js to v3.30.2 2023-05-24 19:23:26 +00:00
renovate[bot]
3950800a3e Update dependency axios to v1.4.0 2023-05-24 15:10:57 +00:00
renovate[bot]
1dfbbf9f6e Update dependency @types/luxon to v3.3.0 2023-05-24 11:21:21 +00:00
renovate[bot]
d0db3c0829 Update module github.com/imdario/mergo to v0.3.15 2023-05-24 06:33:26 +00:00
renovate[bot]
cb233072ca Update module github.com/aws/aws-sdk-go to v1.44.268 2023-05-24 02:20:02 +00:00
renovate[bot]
cba34f34d5 Update module github.com/stretchr/testify to v1.8.3 2023-05-23 16:14:42 +00:00
renovate[bot]
a048ceb503 Update module github.com/aws/aws-sdk-go to v1.44.267 2023-05-23 10:47:26 +00:00
renovate[bot]
df2672f819 Update dependency @koumoul/vjsf to v2.21.4 2023-05-23 07:02:17 +00:00
renovate[bot]
90440cdbf4 Update golang.org/x/exp digest to 2e198f4 2023-05-22 22:13:50 +00:00
renovate[bot]
fad7b4138d Update module github.com/blevesearch/bleve/v2 to v2.3.8 2023-05-22 18:41:49 +00:00
renovate[bot]
a2618fef27 Update dependency vuetify to v2.6.15 2023-05-22 11:36:51 +00:00
renovate[bot]
550df2a831 Update dependency json-schema-editor-vue to v2.2.2 2023-05-22 06:23:45 +00:00
renovate[bot]
b7a9adaa9f Update dependency d3 to v7.8.4 2023-05-22 03:52:47 +00:00
renovate[bot]
9dd0e166e6 Update dependency @vue/test-utils to v2.3.2 2023-05-22 00:38:23 +00:00
renovate[bot]
f47bc8230c Update dependency @types/jest to v29.5.1 2023-05-21 21:36:07 +00:00
renovate[bot]
22fe237fbd Update dependency @types/lodash to v4.14.194 2023-05-21 20:27:35 +02:00
renovate[bot]
065f67ff6c Update dependency @mdi/font to v7.2.96 2023-05-21 17:44:26 +00:00
renovate[bot]
6ea69d6903 Update github.com/icza/dyno digest to 09f820a 2023-05-21 15:09:16 +00:00
renovate[bot]
7d21b06bab Update golang.org/x/exp digest to 03e9162 2023-05-21 14:35:15 +02:00
Jonas Plum
25b9d693af Fix linter issues 2023-05-21 13:52:26 +02:00
Jonas Plum
c96e2ebe06 Change readme 2023-05-21 13:35:41 +02:00
renovate[bot]
41e55756cf Update actions/setup-go action to v4 (#825)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-03-26 11:07:34 +02:00
renovate[bot]
a377a4ccb8 Update module github.com/blevesearch/bleve/v2 to v2.3.7 2023-03-17 22:13:38 +00:00
renovate[bot]
2944ae2100 Update module github.com/aws/aws-sdk-go to v1.44.224 2023-03-17 20:23:19 +00:00
renovate[bot]
519d9152c8 Update module github.com/aws/aws-sdk-go to v1.44.223 2023-03-16 20:55:41 +00:00
renovate[bot]
3769743904 Update dependency @types/jest to v29.5.0 2023-03-16 18:06:30 +00:00
renovate[bot]
d9f4c979de Update module github.com/imdario/mergo to v0.3.14 2023-03-16 10:16:47 +00:00
renovate[bot]
b51d70c586 Update module github.com/aws/aws-sdk-go to v1.44.222 2023-03-16 10:00:41 +00:00
renovate[bot]
6f66584807 Update dependency d3 to v7.8.2 2023-03-16 04:31:12 +00:00
renovate[bot]
6a81e5cdf8 Update dependency @types/jest to v29.4.4 2023-03-16 03:44:57 +00:00
renovate[bot]
7ab504659f Update dependency @crinkles/digl to v2.0.3 2023-03-16 00:54:23 +00:00
renovate[bot]
3b379f9a36 Update golang.org/x/exp digest to 642cace 2023-03-15 22:52:06 +00:00
renovate[bot]
2048312a63 Update dependency @types/jest to v29.4.2 2023-03-15 08:30:50 +00:00
renovate[bot]
358f6d8f38 Update dependency sass to v1.59.3 2023-03-15 00:54:24 +00:00
renovate[bot]
32fd39bac4 Update module github.com/aws/aws-sdk-go to v1.44.221 2023-03-14 21:53:03 +00:00
renovate[bot]
1d1a40d768 Update golang.org/x/exp digest to db07412 2023-03-14 20:46:09 +00:00
renovate[bot]
7b68319912 Update typescript-eslint monorepo to v5.55.0 2023-03-14 06:05:44 +00:00
renovate[bot]
95498051cc Update module github.com/aws/aws-sdk-go to v1.44.220 2023-03-14 04:55:27 +00:00
renovate[bot]
979bbeb468 Update dependency core-js to v3.29.1 2023-03-14 03:56:29 +00:00
renovate[bot]
3c2a247241 Update dependency @types/jest to v29.4.1 2023-03-14 00:38:08 +00:00
renovate[bot]
d5dcd23380 Update dependency sass to v1.59.2 2023-03-11 04:00:09 +00:00
renovate[bot]
f0d5925918 Update module github.com/aws/aws-sdk-go to v1.44.219 2023-03-11 01:40:03 +00:00
renovate[bot]
cc33031751 Update golang.org/x/exp digest to 522b1b5 2023-03-10 21:36:46 +00:00
renovate[bot]
202f015f0d Update module github.com/aws/aws-sdk-go to v1.44.218 2023-03-10 01:52:30 +00:00
renovate[bot]
cc3ce0377f Update dependency @vue/test-utils to v2.3.1 2023-03-09 07:17:19 +00:00
renovate[bot]
fb5002d5fc Update module github.com/aws/aws-sdk-go to v1.44.217 2023-03-08 21:29:17 +00:00
renovate[bot]
3bff4444a2 Update module github.com/aws/aws-sdk-go to v1.44.216 2023-03-08 03:35:18 +00:00
renovate[bot]
1e2efe6ae4 Update golang.org/x/exp digest to 24139be 2023-03-08 00:10:16 +00:00
renovate[bot]
497d3a5b45 Update typescript-eslint monorepo to v5.54.1 2023-03-07 13:41:55 +00:00
renovate[bot]
fd624ebe79 Update module github.com/aws/aws-sdk-go to v1.44.215 2023-03-07 10:17:46 +00:00
renovate[bot]
82d65430e8 Update golang.org/x/exp digest to f0f767c 2023-03-07 06:42:31 +00:00
renovate[bot]
51aa1f6333 Update module golang.org/x/oauth2 to v0.6.0 2023-03-05 07:33:06 +00:00
renovate[bot]
458ec5a5e0 Update dependency luxon to v3.3.0 2023-03-04 18:58:50 +00:00
renovate[bot]
0dc2b31d74 Update golang.org/x/exp digest to 9ff063c 2023-03-04 16:01:29 +00:00
renovate[bot]
215bb60c5b Update module github.com/aws/aws-sdk-go to v1.44.214 2023-03-04 13:35:03 +00:00
renovate[bot]
94ea23ab7f Update golang.org/x/exp digest to 44a13b0 2023-03-04 08:40:02 +00:00
renovate[bot]
dd97863db1 Update module github.com/aws/aws-sdk-go to v1.44.213 2023-03-03 03:21:29 +00:00
renovate[bot]
ca8028e6f3 Update module github.com/aws/aws-sdk-go to v1.44.212 2023-03-02 01:02:08 +00:00
renovate[bot]
dab3c43ed6 Update module github.com/arangodb/go-driver to v1.5.2 2023-03-01 16:21:21 +00:00
renovate[bot]
19ced1c158 Update module github.com/aws/aws-sdk-go to v1.44.211 2023-02-28 22:18:46 +00:00
renovate[bot]
6df41e1725 Update module github.com/aws/aws-sdk-go to v1.44.210 2023-02-28 03:26:02 +00:00
renovate[bot]
5efd237486 Update typescript-eslint monorepo to v5.54.0 2023-02-27 22:49:46 +00:00
renovate[bot]
e3008f3e32 Update dependency core-js to v3.29.0 2023-02-27 00:03:17 +00:00
renovate[bot]
68ff808737 Update module github.com/stretchr/testify to v1.8.2 2023-02-25 17:39:48 +00:00
renovate[bot]
1f6bcfb2f4 Update module github.com/aws/aws-sdk-go to v1.44.209 2023-02-25 06:10:39 +00:00
renovate[bot]
f6093bbf46 Update golang.org/x/exp digest to c95f2b4 2023-02-25 00:48:29 +00:00
renovate[bot]
52da58314a Update module github.com/aws/aws-sdk-go to v1.44.208 2023-02-24 06:35:42 +00:00
renovate[bot]
4a57413229 Update golang.org/x/exp digest to 50820d9 2023-02-24 03:11:53 +00:00
renovate[bot]
197507cf9b Update module github.com/aws/aws-sdk-go to v1.44.207 2023-02-23 08:38:26 +00:00
renovate[bot]
ae36ce5cfb Update dependency axios to v1.3.4 2023-02-23 03:07:44 +00:00
renovate[bot]
4b8691137c Update module github.com/aws/aws-sdk-go to v1.44.206 2023-02-22 03:32:47 +00:00
renovate[bot]
93f39c8c9a Update typescript-eslint monorepo to v5.53.0 2023-02-21 04:32:14 +00:00
renovate[bot]
7845a8406e Update module github.com/aws/aws-sdk-go to v1.44.205 2023-02-21 01:01:12 +00:00
renovate[bot]
5e8813cb60 Update dependency @vue/test-utils to v2.3.0 2023-02-20 05:24:45 +00:00
renovate[bot]
24618b7b43 Update dependency sass to v1.58.3 2023-02-18 06:03:04 +00:00
renovate[bot]
7850e0b95e Update module github.com/aws/aws-sdk-go to v1.44.204 2023-02-18 01:48:44 +00:00
renovate[bot]
994c7e9b29 Update module github.com/arangodb/go-driver to v1.5.0 2023-02-17 18:08:51 +00:00
renovate[bot]
8d3b9b800f Update dependency sass to v1.58.2 2023-02-17 12:36:06 +00:00
renovate[bot]
9bef926de0 Update module github.com/aws/aws-sdk-go to v1.44.203 2023-02-17 06:47:16 +00:00
renovate[bot]
f4a22a1b89 Update module github.com/aws/aws-sdk-go to v1.44.202 2023-02-15 22:45:28 +00:00
renovate[bot]
37bb0f480e Update typescript-eslint monorepo to v5.52.0 2023-02-15 04:52:24 +00:00
renovate[bot]
b26ce2099a Update module github.com/aws/aws-sdk-go to v1.44.201 2023-02-15 02:50:49 +00:00
renovate[bot]
941b10caf7 Update dependency core-js to v3.28.0 2023-02-14 23:41:58 +00:00
renovate[bot]
c3ba3e4f6d Update module github.com/aws/aws-sdk-go to v1.44.200 2023-02-14 18:53:58 +00:00
renovate[bot]
897733b90a Update dependency sass to v1.58.1 2023-02-14 12:52:37 +00:00
renovate[bot]
c6cb853729 Update golang.org/x/exp digest to 5e25df0 2023-02-14 06:55:46 +00:00
renovate[bot]
ae05ef9e44 Update dependency axios to v1.3.3 2023-02-14 02:52:26 +00:00
renovate[bot]
6293b1e876 Update golang.org/x/exp digest to a684f29 2023-02-12 20:08:35 +00:00
renovate[bot]
d35359bd9b Update module github.com/aws/aws-sdk-go to v1.44.199 2023-02-11 05:21:21 +00:00
renovate[bot]
6dcda40b39 Update golang.org/x/exp digest to 062eb4c 2023-02-11 02:07:44 +00:00
renovate[bot]
71b6788582 Update module github.com/aws/aws-sdk-go to v1.44.198 2023-02-10 01:22:02 +00:00
renovate[bot]
4978d9fccf Update module golang.org/x/oauth2 to v0.5.0 2023-02-09 06:00:20 +00:00
renovate[bot]
f7c99548db Update module github.com/aws/aws-sdk-go to v1.44.197 2023-02-09 02:43:10 +00:00
renovate[bot]
d576fbe697 Update module github.com/aws/aws-sdk-go to v1.44.196 2023-02-08 04:05:39 +00:00
renovate[bot]
a81df1f857 Update module github.com/aws/aws-sdk-go to v1.44.195 2023-02-07 06:00:12 +00:00
renovate[bot]
394e505742 Update typescript-eslint monorepo to v5.51.0 2023-02-07 02:13:02 +00:00
renovate[bot]
7223027209 Update golang.org/x/exp digest to 46f607a 2023-02-07 00:01:06 +00:00
renovate[bot]
7eaa98dc5b Update docker/build-push-action action to v4 (#723)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-02-05 02:24:21 +01:00
Jonas Plum
c8b6a6b482 Hide API key when view is changed (#743) 2023-02-05 02:24:05 +01:00
Jonas Plum
aee0be7a68 Allow @ in usernames (#742) 2023-02-05 01:45:33 +01:00
renovate[bot]
188ca256af Update dependency axios to v1.3.2 2023-02-04 05:34:05 +00:00
renovate[bot]
3b7af3bf08 Update module github.com/aws/aws-sdk-go to v1.44.194 2023-02-04 01:25:45 +00:00
renovate[bot]
e0d28bb36b Update golang.org/x/exp digest to 98cc5a0 2023-02-03 21:59:58 +00:00
renovate[bot]
16c4bb3d37 Update module github.com/aws/aws-sdk-go to v1.44.193 2023-02-03 09:59:53 +00:00
renovate[bot]
7a78066000 Update dependency @vue/test-utils to v2.2.10 2023-02-03 06:27:51 +00:00
renovate[bot]
bdb0a638f8 Update dependency axios to v1.3.1 2023-02-03 03:08:01 +00:00
renovate[bot]
b669b0e742 Update golang.org/x/exp digest to 54bba9f 2023-02-02 22:09:55 +00:00
renovate[bot]
ef046cbac1 Update module github.com/aws/aws-sdk-go to v1.44.192 2023-02-02 06:08:24 +00:00
renovate[bot]
0806f320b1 Update dependency @vue/test-utils to v2.2.9 2023-02-02 03:18:29 +00:00
renovate[bot]
0ccdde0522 Update dependency sass to v1.58.0 2023-02-01 09:29:25 +00:00
renovate[bot]
911d60d80f Update dependency axios to v1.3.0 2023-02-01 05:21:08 +00:00
renovate[bot]
f94bc2044f Update module github.com/aws/aws-sdk-go to v1.44.191 2023-02-01 01:50:07 +00:00
renovate[bot]
1e0a3393bf Update golang.org/x/exp digest to f062dba 2023-01-31 21:20:22 +00:00
renovate[bot]
570c413c3d Update typescript-eslint monorepo to v5.50.0 2023-01-31 16:58:20 +00:00
renovate[bot]
fa8494fe0d Update module github.com/aws/aws-sdk-go to v1.44.190 2023-01-31 12:30:49 +00:00
renovate[bot]
3fb22e10ce Update golang.org/x/exp digest to aae9b4e 2023-01-31 08:47:02 +00:00
renovate[bot]
af22af2334 Update dependency typescript to v4.9.5 2023-01-31 04:25:37 +00:00
renovate[bot]
2bf0d511bf Update golang.org/x/exp digest to a960b37 2023-01-29 20:44:37 +00:00
renovate[bot]
bd31f7b2a7 Update dependency axios to v1.2.6 2023-01-28 21:59:23 +00:00
renovate[bot]
123f334c71 Update dependency @vue/test-utils to v2.2.8 2023-01-28 06:22:03 +00:00
renovate[bot]
c3b7fd9f73 Update golang.org/x/exp digest to 31bee51 2023-01-28 02:13:30 +00:00
renovate[bot]
d6104db54f Update module github.com/aws/aws-sdk-go to v1.44.189 2023-01-27 22:50:07 +00:00
renovate[bot]
527cd0d828 Update module github.com/aws/aws-sdk-go to v1.44.188 2023-01-27 07:48:11 +00:00
renovate[bot]
56828c57bd Update dependency axios to v1.2.5 2023-01-27 05:04:35 +00:00
renovate[bot]
81feebcd4e Update golang.org/x/exp digest to a67bb56 2023-01-27 01:37:31 +00:00
renovate[bot]
d56adb814d Update golang.org/x/exp digest to b3c2aaf 2023-01-26 03:58:15 +00:00
renovate[bot]
14b79304e1 Update module github.com/aws/aws-sdk-go to v1.44.187 2023-01-26 00:48:40 +00:00
renovate[bot]
8e9250a1e9 Update dependency @types/jest to v29.4.0 2023-01-25 14:19:22 +00:00
renovate[bot]
43d2d71609 Update module github.com/aws/aws-sdk-go to v1.44.186 2023-01-25 06:49:21 +00:00
renovate[bot]
571af85383 Update golang.org/x/exp digest to d38c7dc 2023-01-25 03:23:28 +00:00
renovate[bot]
ddeff78bb4 Update dependency axios to v1.2.4 2023-01-24 23:26:52 +00:00
renovate[bot]
416d438c4b Update golang.org/x/exp digest to 7f5a42a 2023-01-24 19:35:38 +00:00
renovate[bot]
116aa53b4f Update typescript-eslint monorepo to v5.49.0 2023-01-24 03:57:20 +00:00
renovate[bot]
876cbfde02 Update module github.com/aws/aws-sdk-go to v1.44.185 2023-01-24 00:23:49 +00:00
renovate[bot]
c866758207 Update dependency @koumoul/vjsf to v2.21.3 2023-01-23 20:52:33 +00:00
renovate[bot]
ffd16770b7 Update dependency @koumoul/vjsf to v2.21.2 2023-01-23 12:25:20 +00:00
Jonas Plum
27f1b0df79 Add playbook editor (#702) 2023-01-21 23:25:55 +01:00
renovate[bot]
ee9d906e28 Update module github.com/aws/aws-sdk-go to v1.44.184 2023-01-21 00:27:55 +00:00
renovate[bot]
341097e69e Update module github.com/aws/aws-sdk-go to v1.44.183 2023-01-20 05:33:33 +00:00
renovate[bot]
bfd1cf19aa Update module github.com/aws/aws-sdk-go to v1.44.182 2023-01-19 10:52:11 +00:00
renovate[bot]
ede96d46f3 Update dependency core-js to v3.27.2 2023-01-19 06:38:27 +00:00
renovate[bot]
07045eb3f4 Update dependency @types/jest to v29.2.6 2023-01-19 02:43:25 +00:00
renovate[bot]
9aa0b9dbd2 Update module github.com/tus/tusd to v1.10.1 2023-01-18 22:58:20 +00:00
renovate[bot]
e735667619 Update golang.org/x/exp digest to a68e582 2023-01-18 18:12:58 +00:00
renovate[bot]
971ff2c2cf Update typescript-eslint monorepo to v5.48.2 2023-01-18 10:31:23 +00:00
renovate[bot]
4371883922 Update module github.com/aws/aws-sdk-go to v1.44.181 2023-01-18 06:22:11 +00:00
renovate[bot]
40d134a482 Update module golang.org/x/oauth2 to v0.4.0 2023-01-17 18:11:57 +00:00
renovate[bot]
82962f12ed Update dependency eslint-plugin-jest to v27.2.1 2023-01-17 10:24:55 +01:00
renovate[bot]
ab954f1b2c Update dependency core-js to v3.27.1 2023-01-17 10:24:47 +01:00
renovate[bot]
982102c98a Update dependency luxon to v3.2.1 2023-01-17 10:24:35 +01:00
renovate[bot]
a019615053 Update module github.com/coreos/go-oidc/v3 to v3.5.0 2023-01-17 10:24:13 +01:00
renovate[bot]
23580c5ba2 Update dependency vuetify to v2.6.14 2023-01-17 08:10:03 +00:00
renovate[bot]
2cd98dd1e4 Update dependency json-schema-editor-vue to v2.2.1 2023-01-17 04:11:12 +00:00
renovate[bot]
7817abce77 Update dependency axios to v1.2.2 2023-01-17 00:49:00 +00:00
renovate[bot]
43b0703843 Update dependency @vue/test-utils to v2.2.7 2023-01-16 20:48:24 +00:00
renovate[bot]
82c5ac8b7f Update dependency @types/jest to v29.2.5 2023-01-16 16:30:44 +00:00
renovate[bot]
c72764a507 Update dependency @koumoul/vjsf to v2.21.1 2023-01-16 16:21:55 +00:00
renovate[bot]
f6c8f033eb Update dependency ajv to v8.12.0 (#681)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-16 13:54:50 +01:00
renovate[bot]
dc8061e050 Update module github.com/aws/aws-sdk-go to v1.44.180 (#680)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-01-16 13:52:45 +01:00
renovate[bot]
b0c634f9a1 Update golang.org/x/exp digest to 1de6713 2023-01-16 12:47:51 +00:00
Jonas Plum
df1454e223 Add version flag (#674) 2022-12-27 16:22:00 +01:00
renovate[bot]
8984e509e1 Update dependency json-schema-editor-vue to v2.2.0 2022-12-21 06:46:15 +00:00
renovate[bot]
709d7d43fa Update dependency sass to v1.57.1 2022-12-21 04:05:21 +00:00
renovate[bot]
ca1f9f01f9 Update module github.com/aws/aws-sdk-go to v1.44.164 2022-12-21 01:33:55 +00:00
renovate[bot]
bd07e86379 Update typescript-eslint monorepo to v5.47.0 2022-12-19 22:19:33 +00:00
renovate[bot]
aefb07770c Update dependency just-kebab-case to v4.2.0 2022-12-18 01:17:12 +00:00
renovate[bot]
0c2d6d4da0 Update golang.org/x/exp digest to 3c43f8b 2022-12-17 20:05:13 +00:00
renovate[bot]
70a5244eec Update dependency sass to v1.57.0 2022-12-17 05:26:36 +00:00
renovate[bot]
e616bb25d5 Update module github.com/aws/aws-sdk-go to v1.44.162 2022-12-17 02:08:48 +00:00
renovate[bot]
b1f082cb91 Update module github.com/blevesearch/bleve/v2 to v2.3.6 2022-12-16 08:28:53 +00:00
renovate[bot]
0ed03bf2b9 Update dependency eslint-plugin-jest to v27.1.7 2022-12-16 05:19:58 +00:00
renovate[bot]
25c6f190e6 Update module github.com/aws/aws-sdk-go to v1.44.161 2022-12-16 02:20:38 +00:00
renovate[bot]
48b5853d88 Update golang.org/x/exp digest to 0915cd7 2022-12-15 23:30:24 +00:00
renovate[bot]
07df2affa2 Update module github.com/aws/aws-sdk-go to v1.44.160 2022-12-14 23:19:53 +00:00
renovate[bot]
3b433e5601 Update module github.com/arangodb/go-driver to v1.4.1 2022-12-14 14:12:09 +00:00
renovate[bot]
8fafcb42d0 Update typescript-eslint monorepo to v5.46.1 2022-12-14 04:41:34 +00:00
renovate[bot]
0e3eb7953e Update dependency vuetify to v2.6.13 2022-12-14 02:14:37 +00:00
renovate[bot]
1c3f337e7c Update module github.com/aws/aws-sdk-go to v1.44.159 2022-12-14 00:06:27 +00:00
renovate[bot]
d79407f645 Update golang.org/x/exp digest to fae10dd 2022-12-12 22:18:30 +00:00
renovate[bot]
65aba08ffb Update dependency @mdi/font to v7.1.96 2022-12-11 23:48:23 +00:00
renovate[bot]
37406a11ee Update golang.org/x/exp digest to ad323de 2022-12-11 16:27:31 +00:00
renovate[bot]
6f72fd74ca Update module github.com/aws/aws-sdk-go to v1.44.157 2022-12-09 23:33:33 +00:00
renovate[bot]
d34f2cd300 Update typescript-eslint monorepo to v5.46.0 2022-12-09 12:01:22 +00:00
renovate[bot]
356b55fccc Update dependency sass to v1.56.2 2022-12-09 08:53:44 +00:00
renovate[bot]
6e209cf328 Update golang.org/x/exp digest to 732eee0 2022-12-09 05:22:59 +00:00
renovate[bot]
c5bcf67875 Update module github.com/aws/aws-sdk-go to v1.44.156 2022-12-09 02:55:13 +00:00
renovate[bot]
68fc41b88f Update dependency typescript to v4.9.4 2022-12-08 13:43:01 +00:00
renovate[bot]
2b34fdb3a6 Update golang.org/x/exp digest to 44028be 2022-12-08 10:33:08 +00:00
renovate[bot]
22fa8ec90f Update github.com/warjiang/gojsonschema digest to b076d39 2022-12-08 08:05:26 +00:00
renovate[bot]
82d2b35f61 Update golang.org/x/exp digest to 99ab8fa 2022-12-08 04:47:12 +00:00
renovate[bot]
71a5a4f3d9 Update module github.com/go-chi/chi/v5 to v5.0.8 2022-12-07 20:17:23 +00:00
renovate[bot]
01b6bdba5d Update module github.com/aws/aws-sdk-go to v1.44.154 2022-12-06 22:50:01 +00:00
renovate[bot]
6be20911b0 Update module golang.org/x/oauth2 to v0.3.0 2022-12-06 20:04:16 +00:00
renovate[bot]
c28a49c486 Update typescript-eslint monorepo to v5.45.1 2022-12-06 09:25:35 +00:00
renovate[bot]
f4c0b024a3 Update module github.com/aws/aws-sdk-go to v1.44.153 2022-12-06 07:01:32 +00:00
renovate[bot]
f3c0f69c5f Update golang.org/x/exp digest to 47842c8 2022-12-06 04:05:41 +00:00
renovate[bot]
008f08e5fb Update dependency axios to v1.2.1 2022-12-06 00:44:43 +00:00
renovate[bot]
13f2d10d3d Update dependency @types/jest to v29.2.4 2022-12-05 13:20:41 +00:00
renovate[bot]
47aeae44ab Update dependency @vue/test-utils to v2.2.6 2022-12-05 09:26:11 +00:00
renovate[bot]
a7951cf67f Update golang.org/x/exp digest to 6dcec33 2022-12-04 18:18:11 +00:00
renovate[bot]
23efb200c7 Update module github.com/aws/aws-sdk-go to v1.44.152 2022-12-03 00:21:31 +00:00
renovate[bot]
2b0c6f7286 Update dependency @types/lodash to v4.14.191 2022-12-02 20:59:03 +00:00
renovate[bot]
189b5ed532 Update dependency @vue/test-utils to v2.2.5 2022-12-02 17:13:40 +00:00
renovate[bot]
a89f11efbc Update module github.com/aws/aws-sdk-go to v1.44.149 2022-11-30 01:41:50 +00:00
renovate[bot]
2045b1057e Update module github.com/aws/aws-sdk-go to v1.44.147 2022-11-29 12:48:44 +00:00
renovate[bot]
c84470eb7e Update dependency luxon to v3.1.1 2022-11-29 09:55:52 +00:00
renovate[bot]
06a5c6a688 Update typescript-eslint monorepo to v5.45.0 2022-11-28 21:46:59 +00:00
renovate[bot]
5599c3b035 Update module github.com/aws/aws-sdk-go to v1.44.146 2022-11-28 06:49:28 +00:00
renovate[bot]
da423e3a51 Update golang.org/x/exp digest to 6ab00d0 2022-11-26 19:22:08 +00:00
renovate[bot]
501134de7c Update dependency eslint-plugin-jest to v27.1.6 2022-11-24 23:29:56 +00:00
renovate[bot]
a8a1bfdf8a Update module github.com/aws/aws-sdk-go to v1.44.145 2022-11-24 02:05:04 +00:00
renovate[bot]
a2f0d127d9 Update dependency @types/lodash to v4.14.190 2022-11-23 23:39:49 +00:00
renovate[bot]
2cc371fbde Update dependency axios to v1.2.0 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-23 01:39:50 +01:00
renovate[bot]
b574fcc5d8 Update dependency cypress to v11.2.0 (#617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-11-23 01:39:42 +01:00
Jonas Plum
a6f8259400 Fix playbook editor task schema (#618) 2022-11-23 01:39:32 +01:00
renovate[bot]
f99f7d98c7 Update module github.com/aws/aws-sdk-go to v1.44.144 2022-11-22 22:27:09 +00:00
renovate[bot]
ae09e09b98 Update module github.com/tidwall/gjson to v1.14.4 2022-11-22 06:24:02 +00:00
renovate[bot]
fcc34e6f84 Update module github.com/aws/aws-sdk-go to v1.44.143 2022-11-22 00:25:37 +00:00
renovate[bot]
ad437c5a5a Update typescript-eslint monorepo to v5.44.0 2022-11-21 21:34:54 +00:00
renovate[bot]
220848edd8 Update dependency @vue/test-utils to v2.2.4 2022-11-21 02:23:14 +00:00
renovate[bot]
e6fd4d1f53 Update module github.com/aws/aws-sdk-go to v1.44.142 2022-11-19 02:47:24 +00:00
renovate[bot]
1562977173 Update module github.com/aws/aws-sdk-go to v1.44.140 2022-11-17 23:03:24 +00:00
renovate[bot]
2bd73e6281 Update module github.com/aws/aws-sdk-go to v1.44.139 2022-11-16 23:48:40 +00:00
renovate[bot]
70c5045bbe Update dependency cypress to v11 2022-11-16 09:59:08 +01:00
renovate[bot]
73abe2ef18 Update dependency typescript to v4.9.3 2022-11-16 02:31:58 +00:00
renovate[bot]
eb7b6b6387 Update module github.com/aws/aws-sdk-go to v1.44.138 2022-11-15 23:36:52 +00:00
renovate[bot]
e556cfe0fc Update dependency @vue/test-utils to v2.2.3 2022-11-15 13:26:43 +00:00
renovate[bot]
26a6194583 Update typescript-eslint monorepo to v5.43.0 2022-11-15 10:59:56 +00:00
renovate[bot]
3d32e21170 Update module github.com/aws/aws-sdk-go to v1.44.137 2022-11-15 07:52:39 +00:00
renovate[bot]
8aac1eb6d6 Update dependency @types/lodash to v4.14.189 2022-11-15 05:50:21 +00:00
renovate[bot]
0d67f85313 Update dependency @types/jest to v29.2.3 2022-11-15 02:51:45 +00:00
renovate[bot]
df6b3b9a75 Update golang.org/x/exp digest to 8509921 2022-11-14 23:53:38 +00:00
renovate[bot]
95bb9c1746 Update dependency ajv to v8.11.2 2022-11-14 01:06:48 +00:00
renovate[bot]
bfb8301c65 Update dependency core-js to v3.26.1 2022-11-13 22:44:51 +00:00
renovate[bot]
975138dce7 Update dependency @vue/test-utils to v2.2.2 2022-11-13 12:48:32 +00:00
renovate[bot]
4c3225173e Update module github.com/alecthomas/kong to v0.7.1 2022-11-13 01:37:11 +00:00
renovate[bot]
094051f2f0 Update golang.org/x/exp digest to 129d8d6 2022-11-12 04:06:59 +00:00
renovate[bot]
1a7881ffa2 Update module github.com/aws/aws-sdk-go to v1.44.136 2022-11-12 01:28:22 +00:00
renovate[bot]
7f9e5dcd4e Update golang.org/x/exp digest to ab4555d 2022-11-11 13:09:55 +00:00
renovate[bot]
9bbf237731 Update dependency eslint-plugin-jest to v27.1.5 2022-11-11 02:13:24 +00:00
renovate[bot]
4494f1afdd Update module github.com/aws/aws-sdk-go to v1.44.135 2022-11-11 00:04:21 +00:00
renovate[bot]
2957eee40a Update golang.org/x/exp digest to d0897a7 2022-11-10 21:17:45 +00:00
renovate[bot]
2570aed5d9 Update module golang.org/x/oauth2 to v0.2.0 2022-11-10 15:47:30 +00:00
renovate[bot]
309522eb72 Update golang.org/x/exp digest to fc8884a 2022-11-10 03:19:13 +00:00
renovate[bot]
57830be851 Update module github.com/aws/aws-sdk-go to v1.44.134 2022-11-10 00:17:16 +00:00
renovate[bot]
45f443540d Update golang.org/x/exp digest to 9ce248d 2022-11-09 18:22:22 +00:00
renovate[bot]
f5875ff23e Update dependency sass to v1.56.1 2022-11-09 06:45:47 +00:00
renovate[bot]
182181ae7b Update golang.org/x/exp digest to 5d53382 2022-11-09 04:03:28 +00:00
renovate[bot]
75b87074fb Update module github.com/aws/aws-sdk-go to v1.44.133 2022-11-09 00:38:27 +00:00
renovate[bot]
e01ccc8f90 Update typescript-eslint monorepo to v5.42.1 2022-11-08 09:43:51 +00:00
renovate[bot]
bba495c0a6 Update dependency json-schema-editor-vue to v2.1.2 2022-11-08 06:35:39 +00:00
renovate[bot]
717c4cb48e Update module github.com/aws/aws-sdk-go to v1.44.132 2022-11-08 01:06:15 +00:00
renovate[bot]
838a24ee71 Update golang.org/x/exp digest to f965990 2022-11-06 14:20:57 +01:00
renovate[bot]
0eeafffd5e Update github.com/jonas-plum/maut digest to ed984fd 2022-11-05 19:39:52 +00:00
renovate[bot]
8c2db71732 Update module github.com/aws/aws-sdk-go to v1.44.131 2022-11-05 07:12:29 +00:00
renovate[bot]
d742f28a48 Update dependency vue-axios to v3.5.2 2022-11-05 04:16:08 +00:00
renovate[bot]
507720fd5c Update dependency eslint-plugin-jest to v27.1.4 2022-11-05 01:39:36 +00:00
renovate[bot]
b453a10f2e Update dependency @types/lodash to v4.14.188 2022-11-04 23:27:42 +01:00
renovate[bot]
45fc985807 Update dependency sass to v1.56.0 2022-11-04 23:27:31 +01:00
renovate[bot]
57a5332ce5 Update typescript-eslint monorepo to v5.42.0 2022-11-04 23:27:21 +01:00
renovate[bot]
c23a146aba Update dependency luxon to v3.1.0 2022-11-04 23:27:13 +01:00
renovate[bot]
21bf0be426 Update dependency @types/jest to v29.2.2 2022-11-04 21:44:43 +00:00
renovate[bot]
aaf5f2a98f Update module github.com/aws/aws-sdk-go to v1.44.130 2022-11-03 22:55:33 +00:00
renovate[bot]
7c11f85952 Update module github.com/aws/aws-sdk-go to v1.44.129 2022-11-02 22:51:35 +00:00
renovate[bot]
bb84ebe47d Update module github.com/aws/aws-sdk-go to v1.44.128 2022-11-01 21:17:16 +00:00
renovate[bot]
0b1965263f Update dependency @types/lodash to v4.14.187 2022-11-01 09:33:42 +00:00
renovate[bot]
510d9a1255 Update dependency @types/jest to v29.2.1 2022-11-01 06:15:13 +00:00
renovate[bot]
0f353b9ab1 Update module github.com/aws/aws-sdk-go to v1.44.127 2022-11-01 01:00:53 +00:00
renovate[bot]
f45af557c1 Update golang.org/x/exp digest to c99f073 2022-10-31 23:05:09 +00:00
renovate[bot]
443b946da6 Update dependency vue-axios to v3.5.1 2022-10-29 22:28:40 +00:00
renovate[bot]
6739f75d69 Update module github.com/aws/aws-sdk-go to v1.44.126 2022-10-29 20:59:52 +00:00
renovate[bot]
03377ad1ba Update dependency json-schema-editor-vue to v2.1.1 2022-10-29 18:18:42 +02:00
renovate[bot]
e97ca90906 Update golang.org/x/exp digest to 83b7d23 2022-10-29 18:18:34 +02:00
renovate[bot]
7c2db457ab Update dependency cypress to v10.11.0 2022-10-29 17:59:44 +02:00
renovate[bot]
ba4647857d Update module github.com/alecthomas/kong to v0.7.0 2022-10-29 17:59:28 +02:00
renovate[bot]
c95815c332 Update typescript-eslint monorepo to v5.41.0 2022-10-29 17:59:18 +02:00
renovate[bot]
c4ad7cc48e Update dependency @vue/test-utils to v2.2.1 2022-10-29 17:59:09 +02:00
renovate[bot]
20ab0bbe62 Update dependency core-js to v3.26.0 2022-10-29 17:59:01 +02:00
renovate[bot]
cfbca3c21d Update module github.com/aws/aws-sdk-go to v1.44.123 2022-10-25 23:47:23 +00:00
renovate[bot]
9c415e3bca Update golang.org/x/exp digest to 111beb4 2022-10-25 16:48:44 +00:00
renovate[bot]
57f19e571c Update module github.com/aws/aws-sdk-go to v1.44.122 2022-10-25 01:20:07 +00:00
renovate[bot]
1003421251 Update module github.com/stretchr/testify to v1.8.1 2022-10-24 06:18:50 +00:00
renovate[bot]
9e2d923df7 Update golang.org/x/exp digest to a1e5550 2022-10-23 19:39:30 +00:00
Jonas Plum
83e3a2e725 Improve restore 2022-10-23 02:22:20 +02:00
Jonas Plum
406d8290ab Fix ticket view mounting 2022-10-23 00:03:49 +02:00
Jonas Plum
11323e5622 Fix detail changes 2022-10-23 00:03:49 +02:00
Jonas Plum
73f88e0963 Split correlated and related tickets (#542)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 22:28:44 +02:00
Jonas Plum
f8a11031fb Fix automated root task start (#541)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 22:06:22 +02:00
Jonas Plum
4d0dfba818 Remove malware playbook (#540)
* Remove malware playbook

Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 21:32:18 +02:00
Jonas Plum
6756ce5426 Remove user passwords (#539)
* Remove user passwords

Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 15:12:37 +02:00
Jonas Plum
fb69a1a07b Replace interface{} with any (#538)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 14:50:09 +02:00
Jonas Plum
9200c865f8 Hide userdata fail message (#536)
* Hide userdata fail message

* Change axios imports

Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 14:10:07 +02:00
Jonas Plum
35c250f96b Add UI file upload permissions (#537)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-22 13:37:05 +02:00
renovate[bot]
75788bde04 Update module github.com/aws/aws-sdk-go to v1.44.121 (#535)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-22 00:13:18 +02:00
renovate[bot]
b905ec2982 Update module golang.org/x/oauth2 to v0.1.0 (#532)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-22 00:13:09 +02:00
renovate[bot]
52dcc2d269 Update dependency @types/jest to v29.2.0 (#525)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-22 00:11:37 +02:00
renovate[bot]
2caf404015 Update module github.com/aws/aws-sdk-go to v1.44.120 2022-10-21 00:07:57 +00:00
renovate[bot]
fd75d26d08 Update module github.com/blevesearch/bleve/v2 to v2.3.5 2022-10-20 21:21:02 +00:00
renovate[bot]
35945f8524 Update module github.com/aws/aws-sdk-go to v1.44.119 2022-10-20 00:58:41 +00:00
renovate[bot]
fa09e542dc Update golang.org/x/exp digest to 2094472 2022-10-19 22:28:44 +00:00
renovate[bot]
f70081e395 Update module github.com/aws/aws-sdk-go to v1.44.118 2022-10-19 08:11:31 +00:00
renovate[bot]
cf6bdef3e9 Update dependency vuetify to v2.6.12 2022-10-19 06:02:22 +00:00
renovate[bot]
17406de9b0 Update golang.org/x/exp digest to 02f3b87 2022-10-19 03:09:54 +00:00
renovate[bot]
d0bdd17f47 Update dependency eslint-plugin-jest to v27.1.3 2022-10-19 00:28:44 +00:00
renovate[bot]
3ac8ee562c Update typescript-eslint monorepo to v5.40.1 2022-10-18 02:07:22 +00:00
renovate[bot]
3ec3d739fa Update module github.com/aws/aws-sdk-go to v1.44.117 2022-10-17 23:08:23 +00:00
renovate[bot]
65da5bb774 Update dependency @types/luxon to v3.0.2 2022-10-17 19:22:50 +00:00
renovate[bot]
f165974f19 Update dependency vue-axios to v3.5.0 (#513)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-16 00:49:30 +02:00
renovate[bot]
6078849054 Update dependency axios to v1.1.3 2022-10-15 17:46:29 +00:00
renovate[bot]
87a4429f36 Update dependency eslint-plugin-jest to v27.1.2 2022-10-15 03:21:22 +00:00
renovate[bot]
b3dfa0f4b2 Update module github.com/aws/aws-sdk-go to v1.44.116 2022-10-15 00:45:25 +00:00
Jonas Plum
c3677357a0 Revert "Update vue monorepo" (#518)
This reverts commit fd1f8e2a2a.
2022-10-15 00:00:06 +02:00
renovate[bot]
fd1f8e2a2a Update vue monorepo 2022-10-14 20:28:09 +00:00
renovate[bot]
c0f71795f2 Update golang.org/x/oauth2 digest to 6fdb5e3 2022-10-14 20:25:53 +00:00
renovate[bot]
2696809e88 Update module github.com/aws/aws-sdk-go to v1.44.115 2022-10-13 22:36:59 +00:00
renovate[bot]
004f4f63e8 Update golang.org/x/exp digest to 4de253d (#514)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-13 01:51:01 +02:00
renovate[bot]
a7fab514bb Update golang.org/x/exp digest to 3640c57 2022-10-12 17:55:57 +00:00
renovate[bot]
0442f7cdab Update golang.org/x/exp digest to 59b0eab (#511)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-12 16:43:54 +02:00
renovate[bot]
80c074c3a6 Update golang.org/x/exp digest to a3968a4 2022-10-12 01:42:54 +00:00
renovate[bot]
eb5f40625f Update golang.org/x/exp digest to 1721192 (#508)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-11 22:03:47 +02:00
renovate[bot]
f89a49d79d Update dependency cypress to v10.10.0 (#509)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-11 22:02:16 +02:00
renovate[bot]
b0ce749c1b Update golang.org/x/exp digest to 0220f59 (#505)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-11 17:51:09 +02:00
renovate[bot]
863e8c1e24 Update typescript-eslint monorepo to v5.40.0 (#504)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-11 09:20:05 +02:00
renovate[bot]
5961db2421 Update module github.com/tus/tusd to v1.10.0 (#503)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-11 09:19:49 +02:00
renovate[bot]
3457692e90 Update golang.org/x/exp digest to 3a778c5 2022-10-11 00:01:25 +00:00
renovate[bot]
321534fa47 Update dependency less-loader to v11.1.0 (#499)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-10 10:02:26 +02:00
renovate[bot]
5b60221ecc Update module github.com/aws/aws-sdk-go to v1.44.114 2022-10-07 23:45:55 +00:00
renovate[bot]
20d64fcadb Update dependency axios to v1.1.2 (#500)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 14:57:20 +02:00
renovate[bot]
400b5e95d7 Update golang.org/x/oauth2 digest to b44042a (#436)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 01:11:26 +02:00
renovate[bot]
73db8bc9c8 Update dependency axios to v1.1.0 (#498)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 01:11:15 +02:00
renovate[bot]
c9a3bd2a22 Update dependency @types/jest to v29.1.2 (#495)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 01:11:07 +02:00
renovate[bot]
34a3083cb8 Update module github.com/aws/aws-sdk-go to v1.44.113 (#497)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 00:56:23 +02:00
renovate[bot]
4d918e62dc Update golang.org/x/exp digest to 316c755 (#496)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-07 00:56:17 +02:00
renovate[bot]
5cd93f1655 Update module github.com/aws/aws-sdk-go to v1.44.112 2022-10-06 01:29:39 +00:00
renovate[bot]
02e15a6dcd Update dependency eslint-plugin-jest to v27.1.1 (#493)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-05 23:44:04 +02:00
renovate[bot]
526e441cf4 Update module github.com/arangodb/go-driver to v1.4.0 (#489)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-05 09:27:29 +02:00
renovate[bot]
946b2dc212 Update dependency axios to v1 (#491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-05 09:26:39 +02:00
renovate[bot]
b2213c210c Update golang.org/x/exp digest to b9f4876 2022-10-05 04:11:33 +00:00
renovate[bot]
b6a9636600 Update golang.org/x/exp digest to 4bbd850 (#488)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 23:31:00 +02:00
renovate[bot]
eea0b4461c Update module github.com/aws/aws-sdk-go to v1.44.111 (#487) 2022-10-04 23:20:38 +02:00
renovate[bot]
75d739d988 Update dependency vuetify to v2.6.11 (#486) 2022-10-04 23:20:27 +02:00
Jonas Plum
f902fb83c2 Make curl retry more robust (#485)
* Make curl retry more robust

* Update ubuntu runner
2022-10-04 19:02:16 +02:00
renovate[bot]
a9de5d8a3e Update typescript-eslint monorepo to v5.39.0 (#484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 10:33:05 +02:00
renovate[bot]
b41ca73a02 Update dependency @koumoul/vjsf to v2.21.0 (#480)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 10:09:47 +02:00
renovate[bot]
9fcbcf47f1 Update dependency eslint-plugin-jest to v27.1.0 (#483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-04 10:02:16 +02:00
renovate[bot]
874832238f Update module github.com/aws/aws-sdk-go to v1.44.110 2022-10-04 02:08:10 +00:00
renovate[bot]
0cc82de91d Update dependency core-js to v3.25.5 2022-10-03 22:58:10 +00:00
Jonas Plum
c490ef90de Add authelia dev deployment (#479)
* Add authelia dev deployment
2022-10-03 12:44:30 +02:00
renovate[bot]
8f804305cd Update dependency core-js to v3.25.4 (#475)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-02 23:45:08 +02:00
Jonas Plum
2626d156bc Change roles in user editor (#478)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 23:45:00 +02:00
Jonas Plum
b25f3f4708 Fix version info (#477)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 23:36:34 +02:00
Jonas Plum
5f4fd667a9 Update readme (#476)
* Update readme

Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 23:18:12 +02:00
Jonas Plum
fc42d4043b Fix version (#474)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 20:10:57 +02:00
Jonas Plum
e987e46cbd Remove unneeded CI commands (#472)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 19:40:12 +02:00
Jonas Plum
b085ef4f1b Fix permissions (#471)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 06:40:01 +02:00
Jonas Plum
9915a10ca2 Map userdata (#470)
* Map userdata
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-02 05:41:48 +02:00
renovate[bot]
94ebcade12 Update golang.org/x/exp digest to 540bb73 (#469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-02 04:41:16 +02:00
Jonas Plum
f73e91d142 Add maut (#468)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-01 21:38:13 +02:00
Jonas Plum
4eb0658888 Configure OIDC (#467)
Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-01 03:36:39 +02:00
renovate[bot]
215e56deb1 Update golang.org/x/exp digest to ec3f013 (#435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-10-01 03:24:48 +02:00
Jonas Plum
a50133f6fd Add auth url (#466)
* Add auth url

Co-authored-by: Jonas Plum <git@jonasplum.de>
2022-10-01 03:05:07 +02:00
renovate[bot]
5b5bba30ca Update module github.com/aws/aws-sdk-go to v1.44.109 2022-10-01 01:03:44 +00:00
Jonas Plum
2a6183b368 Enable OIDC by default (#432)
* Enable OIDC by default
2022-09-30 21:27:43 +02:00
renovate[bot]
3f6cd5b366 Update dependency @koumoul/vjsf to v2.20.3 (#463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 14:43:01 +02:00
renovate[bot]
24eb325058 Update dependency json-schema-editor-vue to v2.1.0 (#459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:22:04 +02:00
renovate[bot]
210ab54a8c Update dependency core-js to v3.25.3 (#457)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:21:56 +02:00
renovate[bot]
a59dc977d0 Update dependency cypress to v10.9.0 (#458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:21:49 +02:00
renovate[bot]
88191fd7e6 Update dependency sass to v1.55.0 (#460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:21:37 +02:00
renovate[bot]
1ecba482ef Update typescript-eslint monorepo to v5.38.1 (#462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:21:30 +02:00
renovate[bot]
213ecd03d3 Update dependency typescript to v4.8.4 (#461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 12:21:20 +02:00
renovate[bot]
3525582f3f Update dependency vue-router to v3.6.5 (#452)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:39:40 +02:00
renovate[bot]
b14a0c5e77 Update dependency antlr4 to v4.11.0 (#456)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:39:28 +02:00
renovate[bot]
b3f9789801 Update dependency @vue/test-utils to v2.1.0 (#455)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:39:18 +02:00
renovate[bot]
7c800ec8f2 Update dependency eslint-plugin-jest to v27 (#450)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:00:47 +02:00
renovate[bot]
654f188f11 Update dependency @types/jest to v29 (#449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:00:25 +02:00
renovate[bot]
8338ba9f69 Update dependency vuetify to v2.6.10 (#453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 10:00:06 +02:00
renovate[bot]
9dd8116dd8 Update dependency vue-cli-plugin-vuetify to v2.5.8 2022-09-30 05:32:06 +00:00
renovate[bot]
ba689f2482 Update dependency @vue/compiler-sfc to v3.2.40 2022-09-30 02:44:44 +00:00
renovate[bot]
255f668757 Update dependency luxon to v3.0.4 (#448) 2022-09-30 01:44:06 +02:00
renovate[bot]
29ea4145a2 Update dependency @koumoul/vjsf to v2.20.2 (#442)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 01:12:13 +02:00
Jonas Plum
efaf0ed266 Multiple updates (#445) 2022-09-30 00:50:47 +02:00
renovate[bot]
522e93c8f1 Update dependency @types/lodash to v4.14.186 (#438)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-30 00:43:49 +02:00
Jonas Plum
79c2450958 Merge pull request #437 from SecurityBrewery/renovate/jest-monorepo
Update dependency @types/jest to v28.1.8
2022-09-30 00:25:26 +02:00
renovate[bot]
06e57b6e4c Update dependency @types/jest to v28.1.8 2022-09-29 22:00:19 +00:00
Jonas Plum
7275202f63 Merge pull request #431 from SecurityBrewery/renovate/golang.org-x-crypto-digest
Update golang.org/x/crypto digest to eccd636
2022-09-29 23:56:03 +02:00
renovate[bot]
b2d2fe25e1 Update golang.org/x/crypto digest to eccd636 2022-09-29 21:46:20 +00:00
Jonas Plum
59fcd3ce35 Merge pull request #433 from SecurityBrewery/server-timeout
Add server timeout
2022-09-29 23:43:25 +02:00
Jonas Plum
52cebf8d0a Add server timeout 2022-09-29 23:30:22 +02:00
renovate[bot]
979212f16a Update vue monorepo to v2.7.10 2022-08-23 05:47:39 +00:00
renovate[bot]
6093019c4a Update golang.org/x/oauth2 digest to 0ebed06 2022-08-23 03:05:19 +00:00
renovate[bot]
0f45821394 Update typescript-eslint monorepo to v5.34.0 (#424)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-22 21:27:15 +02:00
renovate[bot]
c93853a2bd Update module github.com/aws/aws-sdk-go to v1.44.82 (#423)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-22 21:27:03 +02:00
renovate[bot]
dc94bc59fb Update dependency vue-router to v3.6.0 (#422)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-22 17:39:45 +02:00
renovate[bot]
4d299554c5 Update dependency @types/luxon to v3 (#353)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-22 10:26:25 +02:00
renovate[bot]
f661b548d6 Update dependency eslint-plugin-jest to v26.8.7 2022-08-22 02:29:02 +00:00
renovate[bot]
91775bd09b Update nginx Docker tag to v1.23 (#417)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 23:28:27 +02:00
renovate[bot]
1fc358a989 Update typescript-eslint monorepo to v5.33.1 (#418)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 23:28:15 +02:00
renovate[bot]
20e8285816 Update dependency eslint-plugin-jest to v26.8.6 (#415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 23:03:29 +02:00
Jonas Plum
fd8e793361 Fix sorting on multiple ticket fields (#412) 2022-08-21 22:23:27 +02:00
Jonas Plum
2b7be7c212 Add stale.yml (#413) 2022-08-21 22:21:35 +02:00
renovate[bot]
ae913f7cd4 Update dependency cypress to v10.6.0 (#411)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 21:40:23 +02:00
renovate[bot]
f9f2c17709 Update vue monorepo to v2.7.9 (#409)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 21:40:04 +02:00
renovate[bot]
6354fd2735 Update golang.org/x/oauth2 digest to 8227340 (#407)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 21:08:05 +02:00
renovate[bot]
4e49835add Update module github.com/aws/aws-sdk-go to v1.44.81 (#405)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 21:07:58 +02:00
renovate[bot]
26246ce9f3 Update dependency @types/lodash to v4.14.184 (#401)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 21:07:52 +02:00
renovate[bot]
bcb5d2bf78 Update dependency eslint-plugin-jest to v26.8.5 (#392)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 20:38:59 +02:00
renovate[bot]
d80da3e77d Update dependency vue-cli-plugin-vuetify to v2.5.4 (#403)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 20:34:58 +02:00
renovate[bot]
8693ccd489 Update dependency @types/jest to v28.1.7 (#400)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 20:34:45 +02:00
renovate[bot]
ce77fbad47 Update golang.org/x/crypto digest to bc19a97 (#398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 20:34:28 +02:00
Jonas Plum
c93ec52986 Add test retries (#406) 2022-08-21 20:16:56 +02:00
renovate[bot]
cee31465c7 Update github.com/icza/dyno digest to f0b6f8a (#397)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 20:05:21 +02:00
renovate[bot]
4baf1358f1 Update dependency sass to v1.54.5 (#402)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 19:35:03 +02:00
renovate[bot]
603029909d Update dependency vuetify to v2.6.9 (#404)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 19:34:50 +02:00
renovate[bot]
add7730036 Update dependency @koumoul/vjsf to v2.18.2 (#399)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 19:34:42 +02:00
renovate[bot]
cda8295c67 Update github.com/antlr/antlr4/runtime/Go/antlr digest to bc8df83 2022-08-21 15:04:16 +00:00
renovate[bot]
77cd336377 Update dependency just-kebab-case to v4.1.1 2022-08-21 12:25:53 +00:00
renovate[bot]
459fb2f5e7 Update dependency @mdi/font to v7 (#356)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 12:46:27 +02:00
renovate[bot]
e84ba818c5 Update module github.com/tidwall/sjson to v1.2.5 2022-08-21 10:35:05 +00:00
Jonas Plum
fb1de82382 Improve cypress (#395) 2022-08-21 12:23:59 +02:00
renovate[bot]
900d0e8693 Update module github.com/tidwall/gjson to v1.14.3 (#390)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 11:53:45 +02:00
Jonas Plum
0a172c0290 Fmt with 1.19 (#394) 2022-08-21 11:33:25 +02:00
renovate[bot]
baa74761ad Update module github.com/aws/aws-sdk-go to v1.44.70 2022-08-04 21:39:24 +00:00
renovate[bot]
c12c5fe768 Update module github.com/aws/aws-sdk-go to v1.44.69 2022-08-03 22:28:06 +00:00
renovate[bot]
e063ab0936 Update module go to 1.19 (#384)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-03 10:08:38 +02:00
renovate[bot]
b4c20f670c Update dependency sass to v1.54.1 2022-08-03 05:43:16 +00:00
renovate[bot]
b86c2b2efe Update dependency cypress to v10.4.0 (#383)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-02 20:30:00 +02:00
renovate[bot]
09800662ff Update module github.com/aws/aws-sdk-go to v1.44.67 (#380)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-01 22:50:38 +02:00
renovate[bot]
40d63d8dee Update typescript-eslint monorepo to v5.32.0 (#381)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-01 22:49:44 +02:00
renovate[bot]
98b36f3a58 Update dependency eslint-plugin-jest to v26.7.0 2022-07-30 04:06:11 +00:00
renovate[bot]
9c9c23fa0d Update module github.com/aws/aws-sdk-go to v1.44.66 2022-07-30 01:51:34 +00:00
renovate[bot]
44b7744bb7 Update dependency vuetify to v2.6.8 2022-07-29 23:16:51 +00:00
renovate[bot]
e490deff76 Update dependency core-js to v3.24.1 2022-07-29 20:54:31 +00:00
renovate[bot]
3fbbc2ff65 Update dependency core-js to v3.24.0 2022-07-29 03:37:12 +00:00
renovate[bot]
6b21bc72e6 Update dependency json-schema-editor-vue to v2.0.4 2022-07-29 01:53:36 +00:00
renovate[bot]
f3cfa6688d Update dependency vuetify-loader to v1.9.2 2022-07-28 23:44:22 +00:00
renovate[bot]
bc287b7600 Update typescript-eslint monorepo to v5.31.0 (#373)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 23:52:37 +02:00
renovate[bot]
8e5e50c85a Update module github.com/aws/aws-sdk-go to v1.44.65 (#374)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 23:52:25 +02:00
renovate[bot]
ca5f02d94a Update module github.com/arangodb/go-driver to v1.3.3 2022-07-28 21:14:13 +00:00
renovate[bot]
b283717321 Update module github.com/tus/tusd to v1.9.1 2022-07-28 18:31:20 +00:00
renovate[bot]
edb575c2aa Update golang.org/x/exp digest to a9213ee 2022-07-28 15:47:43 +00:00
renovate[bot]
c8f32ff0a7 Update module github.com/aws/aws-sdk-go to v1.44.64 2022-07-28 13:23:07 +00:00
renovate[bot]
e77a311340 Update vue monorepo to v2.7.8 2022-07-23 06:13:42 +00:00
renovate[bot]
b8da0734ec Update dependency sass to v1.54.0 2022-07-23 04:01:57 +00:00
renovate[bot]
9744b8ba96 Update github.com/antlr/antlr4/runtime/Go/antlr digest to 14703f2 2022-07-23 01:43:13 +00:00
renovate[bot]
3d95fece81 Update golang.org/x/oauth2 digest to 128564f 2022-07-22 23:59:45 +00:00
renovate[bot]
210fdeb88d Update dependency @koumoul/vjsf to v2.18.0 2022-07-22 22:01:16 +00:00
renovate[bot]
8914c97c57 Update golang.org/x/crypto digest to 630584e 2022-07-22 19:39:47 +00:00
renovate[bot]
d75d4efdd1 Update module github.com/aws/aws-sdk-go to v1.44.60 2022-07-22 00:00:43 +00:00
renovate[bot]
6cafad44c5 Update dependency cypress to v10.3.1 2022-07-21 01:01:13 +00:00
renovate[bot]
25d67f40af Update dependency swagger-ui to v4.13.0 2022-07-20 22:59:13 +00:00
renovate[bot]
f01e1b197f Update module github.com/aws/aws-sdk-go to v1.44.59 (#357)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-20 22:24:08 +02:00
renovate[bot]
6798427d65 Update dependency @types/luxon to v2.4.0 2022-07-20 17:59:50 +00:00
renovate[bot]
16e95df060 Update golang.org/x/oauth2 digest to c8730f7 2022-07-20 15:19:28 +00:00
renovate[bot]
664e079016 Update typescript-eslint monorepo to v5.30.7 2022-07-20 12:54:29 +00:00
renovate[bot]
5e67816351 Update module github.com/aws/aws-sdk-go to v1.44.58 2022-07-20 10:32:20 +00:00
renovate[bot]
abd6632578 Update dependency core-js to v3.23.5 2022-07-17 21:50:01 +00:00
renovate[bot]
50cc19ddf7 Update dependency @testing-library/vue to v6.6.1 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-17 00:27:13 +02:00
renovate[bot]
7cef009e48 Update vue monorepo to v2.7.7 2022-07-16 19:03:57 +00:00
renovate[bot]
bba0b82034 Update dependency @types/jest to v28.1.6 2022-07-16 03:12:44 +00:00
renovate[bot]
91d96b45e3 Update github.com/antlr/antlr4/runtime/Go/antlr digest to f1df316 2022-07-16 00:47:14 +00:00
renovate[bot]
0fe5ef0784 Update module github.com/aws/aws-sdk-go to v1.44.56 2022-07-15 22:22:19 +00:00
renovate[bot]
0652512d86 Update vue monorepo to v2.7.6 2022-07-15 20:07:06 +00:00
renovate[bot]
7d19621807 Update module github.com/aws/aws-sdk-go to v1.44.55 (#339)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-15 08:27:34 +02:00
renovate[bot]
3a18988ee6 Update dependency eslint-plugin-jest to v26.6.0 2022-07-15 03:07:19 +00:00
renovate[bot]
7fe6f779e1 Update module github.com/aws/aws-sdk-go to v1.44.54 (#338)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-14 00:06:07 +02:00
renovate[bot]
bc31882e16 Update golang.org/x/exp digest to 79cabaa 2022-07-13 18:43:54 +00:00
renovate[bot]
67339e18a4 Update vue monorepo to v2.7.5 (#336)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-13 10:31:49 +02:00
renovate[bot]
eb373c3773 Update dependency @types/jest to v28.1.5 2022-07-13 04:35:25 +00:00
renovate[bot]
42d4d68320 Update module github.com/aws/aws-sdk-go to v1.44.53 (#334)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-12 23:17:20 +02:00
renovate[bot]
8f65b99f26 Update dependency vuetify-loader to v1.9.1 2022-07-12 13:46:02 +00:00
renovate[bot]
4b19642a26 Update module github.com/aws/aws-sdk-go to v1.44.52 2022-07-11 21:24:00 +00:00
renovate[bot]
26f5e11b61 Update typescript-eslint monorepo to v5.30.6 (#331)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 20:48:32 +02:00
renovate[bot]
4c6f17670a Update dependency luxon to v3 (#329)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 14:13:24 +02:00
renovate[bot]
c85b507c28 Update dependency json-schema-editor-vue to v2.0.3 (#330)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 13:59:19 +02:00
renovate[bot]
736cc24f74 Update dependency luxon to v2.5.0 2022-07-09 22:55:24 +00:00
renovate[bot]
37fea143d1 Update dependency core-js to v3.23.4 2022-07-09 20:48:44 +00:00
renovate[bot]
947bb4ba34 Update module github.com/aws/aws-sdk-go to v1.44.51 2022-07-08 22:00:20 +00:00
renovate[bot]
49ad9f74f2 Update vue monorepo to v2.7.4 (#325)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-08 12:36:03 +02:00
renovate[bot]
c0b6626301 Update module github.com/aws/aws-sdk-go to v1.44.50 2022-07-07 23:05:52 +00:00
renovate[bot]
2f3acc36f2 Update module github.com/aws/aws-sdk-go to v1.44.49 2022-07-07 01:15:38 +00:00
renovate[bot]
c1b05b9775 Update golang.org/x/exp digest to b4a6d95 2022-07-06 22:16:30 +00:00
renovate[bot]
3445d4ed8e Update vue monorepo to v2.7.3 2022-07-06 12:32:36 +00:00
renovate[bot]
b9b98dad7d Update module github.com/aws/aws-sdk-go to v1.44.48 2022-07-05 23:06:43 +00:00
renovate[bot]
d5fbff5042 Update vue monorepo to v2.7.2 2022-07-05 20:31:21 +00:00
renovate[bot]
97b30b2abb Update dependency @koumoul/vjsf to v2.17.0 (#317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-05 19:56:33 +02:00
renovate[bot]
07ae6fba2d Update dependency vuetify-loader to v1.9.0 (#318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-05 19:56:19 +02:00
renovate[bot]
f66897289b Update typescript-eslint monorepo to v5.30.5 2022-07-04 20:34:40 +00:00
renovate[bot]
a3e33edec6 Update dependency @vue/test-utils to v2.0.2 2022-07-04 15:47:28 +00:00
renovate[bot]
10240b559c Update typescript-eslint monorepo to v5.30.4 2022-07-03 16:06:49 +00:00
renovate[bot]
c4cb632185 Update module github.com/aws/aws-sdk-go to v1.44.47 (#313)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-01 23:32:36 +02:00
renovate[bot]
9a0fad5f3f Update typescript-eslint monorepo to v5.30.3 2022-07-01 19:48:52 +00:00
renovate[bot]
ae5b7305c8 Update module github.com/aws/aws-sdk-go to v1.44.46 2022-07-01 02:37:50 +00:00
renovate[bot]
3d4401cdcc Update dependency @types/jest to v28.1.4 2022-06-30 23:34:24 +00:00
renovate[bot]
a4148f673b Update golang.org/x/oauth2 digest to 2104d58 2022-06-30 21:00:07 +00:00
renovate[bot]
63cca38fcb Update dependency @koumoul/vjsf to v2.16.1 2022-06-30 17:52:11 +00:00
renovate[bot]
812bd6f782 Update dependency @mdi/font to v6.9.96 (#306)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-30 08:10:11 +02:00
renovate[bot]
8d3cae82a5 Update module github.com/aws/aws-sdk-go to v1.44.45 (#305)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-30 00:23:03 +02:00
Jonas Plum
f21dde77b6 Downgrade to 10.2.0 (#304) 2022-06-29 22:52:46 +02:00
renovate[bot]
1a9215673b Update module github.com/stretchr/testify to v1.8.0 (#303)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-29 20:49:41 +02:00
renovate[bot]
d4c73b603f Update dependency cypress to v10.3.0 (#298)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-29 15:44:06 +02:00
renovate[bot]
69631c7062 Update dependency vuetify to v2.6.7 (#301)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-29 15:43:53 +02:00
renovate[bot]
7318f34cbb Update dependency @koumoul/vjsf to v2.16.0 (#302)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-29 15:43:28 +02:00
renovate[bot]
88b1776ad9 Update dependency @mdi/font to v6.8.96 (#300)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-29 15:04:37 +02:00
renovate[bot]
461e501267 Update golang.org/x/oauth2 digest to 02e64fa 2022-06-29 01:03:40 +00:00
renovate[bot]
23e85d5c9e Update module github.com/aws/aws-sdk-go to v1.44.44 2022-06-28 22:39:02 +00:00
renovate[bot]
42eb593f5a Update vue monorepo to v4.5.19 2022-06-28 10:48:35 +00:00
renovate[bot]
676ef788b1 Update module github.com/aws/aws-sdk-go to v1.44.43 2022-06-28 00:01:51 +00:00
renovate[bot]
2e341f8d55 Update typescript-eslint monorepo to v5.30.0 2022-06-27 20:42:03 +00:00
renovate[bot]
0040c1b6e1 Update github.com/antlr/antlr4/runtime/Go/antlr digest to 9abda18 (#293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-27 00:19:08 +03:00
renovate[bot]
1c59dfdb44 Update dependency core-js to v3.23.3 2022-06-25 23:24:53 +00:00
renovate[bot]
3943474c09 Update github.com/antlr/antlr4/runtime/Go/antlr digest to e4cec20 2022-06-25 20:43:03 +00:00
Jonas Plum
e679781981 Fix static path (#289) 2022-06-25 01:04:42 +02:00
Jonas Plum
b2fde8f26a Update antlr (#287) 2022-06-24 22:30:28 +02:00
renovate[bot]
9ff10e1f34 Update module github.com/aws/aws-sdk-go to v1.44.42 (#285)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-24 21:38:14 +02:00
renovate[bot]
227c3f4f4e Update module github.com/stretchr/testify to v1.7.5 (#284)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-24 09:43:28 +03:00
renovate[bot]
c7e9957749 Update module github.com/aws/aws-sdk-go to v1.44.41 2022-06-24 05:22:56 +00:00
renovate[bot]
ba8e39e8b1 Update dependency sass to v1.53.0 (#280)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-23 08:42:33 +03:00
renovate[bot]
2895630770 Update module github.com/aws/aws-sdk-go to v1.44.40 2022-06-23 04:04:04 +00:00
renovate[bot]
ea0672d8bc Update golang.org/x/crypto digest to 0559593 2022-06-23 01:35:39 +00:00
renovate[bot]
c6000ab54c Update golang.org/x/oauth2 digest to fd043fe 2022-06-22 22:25:24 +00:00
renovate[bot]
ba7b6e685a Update dependency @koumoul/vjsf to v2.15.0 2022-06-22 19:58:12 +00:00
renovate[bot]
6be51a40d6 Update dependency cypress to v10.2.0 2022-06-22 06:34:52 +00:00
renovate[bot]
4d3a8fb857 Update module github.com/aws/aws-sdk-go to v1.44.39 2022-06-22 03:56:12 +00:00
renovate[bot]
edb462592d Update dependency @types/jest to v28.1.3 2022-06-22 00:55:38 +00:00
renovate[bot]
6de65c75f1 Update dependency vuetify-loader to v1.8.0 2022-06-21 14:48:22 +00:00
renovate[bot]
0fd4bd4919 Update typescript-eslint monorepo to v5.29.0 2022-06-21 04:49:16 +00:00
renovate[bot]
0867671a15 Update module github.com/stretchr/testify to v1.7.4 2022-06-21 01:39:11 +00:00
renovate[bot]
ee37b68604 Update dependency core-js to v3.23.2 2022-06-20 22:20:49 +00:00
renovate[bot]
5af2fa9cf2 Update module github.com/aws/aws-sdk-go to v1.44.38 2022-06-20 21:40:14 +00:00
renovate[bot]
9671eccd2b Update module github.com/stretchr/testify to v1.7.3 2022-06-20 19:05:08 +00:00
renovate[bot]
d186abe251 Update dependency typescript to v4.7.4 (#264)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-18 13:06:30 +02:00
renovate[bot]
f10eba7f90 Update dependency @types/jest to v28.1.2 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-18 13:03:51 +02:00
renovate[bot]
9b60c44e4d Update module github.com/aws/aws-sdk-go to v1.44.37 (#265)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-06-18 13:03:43 +02:00
Renovate Bot
1976ed403a Update module github.com/aws/aws-sdk-go to v1.44.36 2022-06-16 22:29:26 +00:00
Renovate Bot
299b094f54 Update vue monorepo to v4.5.18 2022-06-16 19:43:26 +00:00
renovate[bot]
d1f619b861 Update module github.com/aws/aws-sdk-go to v1.44.35 (#260)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-15 23:50:51 +02:00
renovate[bot]
8343f29562 Update module github.com/alecthomas/kong to v0.6.1 (#259)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-15 17:13:31 +02:00
Jonas Plum
2026cb3c6a More e2d tests (#258) 2022-06-15 04:03:22 +02:00
Renovate Bot
2ac1dd29ad Update module github.com/aws/aws-sdk-go to v1.44.34 2022-06-15 00:47:32 +00:00
renovate[bot]
ed5d3d2cf9 Update dependency @vue/test-utils to v2.0.1 (#257)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-15 02:32:10 +02:00
renovate[bot]
12ed0d0f30 Update dependency @types/jest to v28 (#252)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:46:04 +02:00
renovate[bot]
885a4e3c13 Update dependency swagger-ui to v4.12.0 (#254)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:45:56 +02:00
renovate[bot]
d5b944e00d Update typescript-eslint monorepo to v5.28.0 (#255)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:45:40 +02:00
renovate[bot]
0577e7c347 Update module github.com/alecthomas/kong to v0.6.0 (#251)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:20:30 +02:00
renovate[bot]
af690832eb Update dependency eslint-plugin-jest to v26.5.3 (#250)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:18:57 +02:00
renovate[bot]
6a5f6b3320 Update dependency core-js to v3.23.1 (#249)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:16:42 +02:00
Renovate Bot
043460d4e5 Update dependency @testing-library/vue to v6.6.0 2022-06-14 14:15:22 +00:00
Jonas Plum
4b36b8eb1f Enable renovate dashboard again 2022-06-14 16:04:01 +02:00
renovate[bot]
a9178ed44c Update module github.com/stretchr/testify to v1.7.2 (#246)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:02:00 +02:00
renovate[bot]
9bff8d2d09 Update dependency vue-cli-plugin-vuetify to v2.5.1 (#243)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:01:04 +02:00
renovate[bot]
004ff933ba Update dependency @koumoul/vjsf to v2.14.0 (#247)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 16:00:56 +02:00
Jonas Plum
7caf676571 Upgrade Cypress (#245) 2022-06-14 15:59:35 +02:00
renovate[bot]
48459b8a8b Update module github.com/aws/aws-sdk-go to v1.44.33 (#244)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 15:20:07 +02:00
renovate[bot]
2abbb482da Update dependency typescript to v4.7.3 (#242)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 15:19:47 +02:00
renovate[bot]
7ab37efc0c Update dependency sass to v1.52.3 (#241)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 15:19:37 +02:00
Renovate Bot
f304d1e492 Update dependency less to v4.1.3 2022-06-14 07:00:21 +00:00
Renovate Bot
38d3252b2e Update dependency just-kebab-case to v4.0.3 2022-06-14 03:33:34 +00:00
renovate[bot]
a91fd8cccd Update dependency @types/jest to v27.5.2 (#237)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 01:20:59 +02:00
renovate[bot]
1a3c690f79 Update dependency @vue/compiler-sfc to v3.2.37 (#238)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 01:20:48 +02:00
renovate[bot]
7e7290393c Update golang.org/x/oauth2 digest to d0670ef (#236)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-14 00:48:54 +02:00
renovate[bot]
e403cb34f9 Update dependency nginx to v1.22 (#224)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-13 23:30:39 +02:00
renovate[bot]
8c26dc72b4 Update dependency arangodb/arangodb to v3.9.1 (#56)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-13 23:30:19 +02:00
renovate[bot]
224b8c5c42 Update dependency @vue/test-utils to v2 (#206)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-13 23:29:47 +02:00
renovate[bot]
705f0cadea Update golang.org/x/crypto digest to 793ad66 (#235)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-06-13 23:29:26 +02:00
Renovate Bot
be2ab900dc Update golang.org/x/exp digest to b0d7811 2022-06-13 21:10:27 +00:00
Jonas Plum
9f1041d7ef Add simple auth (#186) 2022-06-13 18:13:31 +02:00
Renovate Bot
4883646f39 Update dependency eslint-plugin-jest to v26.4.5 2022-05-29 23:46:07 +00:00
Renovate Bot
6b381d46a8 Update dependency eslint-plugin-jest to v26.4.2 2022-05-28 23:28:29 +00:00
Renovate Bot
6a7b28c294 Update dependency eslint-plugin-jest to v26.3.0 2022-05-28 09:58:50 +00:00
Renovate Bot
2a46041f07 Update module github.com/aws/aws-sdk-go to v1.44.24 2022-05-27 20:44:24 +00:00
Renovate Bot
781f16286d Update module gopkg.in/yaml.v3 to v3.0.1 2022-05-27 13:25:11 +00:00
Renovate Bot
6ad2c83fa0 Update module github.com/aws/aws-sdk-go to v1.44.23 2022-05-26 21:52:57 +00:00
Renovate Bot
efd63a0151 Update module github.com/imdario/mergo to v0.3.13 2022-05-26 00:27:24 +00:00
Renovate Bot
cef947d2f8 Update module github.com/aws/aws-sdk-go to v1.44.22 2022-05-25 21:59:14 +00:00
Renovate Bot
baaa6c989f Update dependency @koumoul/vjsf to v2.13.1 2022-05-25 18:57:20 +00:00
Renovate Bot
6e8ed2cab6 Update dependency typescript to v4.7.2 2022-05-25 04:42:35 +00:00
Renovate Bot
f374a927e4 Update golang.org/x/oauth2 digest to 622c5d5 2022-05-25 02:24:25 +00:00
Renovate Bot
288dfeae5c Update module github.com/aws/aws-sdk-go to v1.44.21 2022-05-24 23:19:58 +00:00
Renovate Bot
86bc0b085e Update dependency core-js to v3.22.7 2022-05-24 20:58:28 +00:00
Renovate Bot
b492897999 Update typescript-eslint monorepo to v5.26.0 2022-05-24 03:59:22 +00:00
Renovate Bot
648ad78e07 Update dependency cypress to v9.7.0 2022-05-24 01:54:55 +00:00
Renovate Bot
76322cc757 Update module github.com/aws/aws-sdk-go to v1.44.20 2022-05-23 23:37:05 +00:00
Renovate Bot
8bc6c473f4 Update dependency @vue/compiler-sfc to v3.2.36 2022-05-23 10:24:22 +00:00
Renovate Bot
298906cae0 Update dependency core-js to v3.22.6 2022-05-22 22:52:56 +00:00
Renovate Bot
9a658d6206 Update dependency vue-cli-plugin-vuetify to v2.5.0 2022-05-22 17:01:38 +00:00
Renovate Bot
e819d917ac Update module gopkg.in/yaml.v3 to v3.0.0 2022-05-21 15:57:53 +00:00
Renovate Bot
529c5eb4c1 Update dependency sass to v1.52.1 2022-05-21 05:07:33 +00:00
Renovate Bot
3d150ac002 Update dependency @vue/compiler-sfc to v3.2.35 2022-05-21 02:18:42 +00:00
Renovate Bot
390318c038 Update module github.com/aws/aws-sdk-go to v1.44.19 2022-05-21 00:03:44 +00:00
Renovate Bot
bbcd66eec8 Update dependency @koumoul/vjsf to v2.13.0 2022-05-20 18:31:23 +00:00
Renovate Bot
0f427c059d Update dependency sass to v1.52.0 2022-05-20 03:08:22 +00:00
Renovate Bot
8eb8b37abd Update module github.com/aws/aws-sdk-go to v1.44.18 2022-05-19 21:45:50 +00:00
renovate[bot]
9585ec23c5 Update dependency swagger-ui to v4 (#146)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-19 08:48:03 +02:00
renovate[bot]
36d1ba049a Update dependency less-loader to v11 (#198)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-19 08:47:47 +02:00
renovate[bot]
d0df4d77f3 Update dependency @vue/compiler-sfc to v3.2.34 (#203)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-19 08:47:26 +02:00
Renovate Bot
e994907970 Update module github.com/aws/aws-sdk-go to v1.44.17 2022-05-19 01:57:53 +00:00
Renovate Bot
99134ee3ae Update golang.org/x/exp digest to 0b5c67f 2022-05-18 23:30:32 +00:00
Renovate Bot
3d27413b8f Update dependency vuetify to v2.6.6 2022-05-18 03:27:03 +00:00
Renovate Bot
7acb853e1a Update module github.com/aws/aws-sdk-go to v1.44.16 2022-05-17 22:19:01 +00:00
Renovate Bot
65bec75b6b Update typescript-eslint monorepo to v5.25.0 2022-05-17 19:18:05 +00:00
Renovate Bot
d1511db529 Update module github.com/arangodb/go-driver to v1.3.2 2022-05-17 13:57:54 +00:00
Renovate Bot
7aee08ed32 Update dependency @mdi/font to v6.7.96 2022-05-17 10:58:34 +00:00
Renovate Bot
3a984f5591 Update module github.com/aws/aws-sdk-go to v1.44.15 2022-05-17 04:47:40 +00:00
Renovate Bot
01ed124f98 Update dependency vue-router to v3.5.4 2022-05-17 02:45:13 +00:00
Renovate Bot
2ccf3ce351 Update dependency @koumoul/vjsf to v2.12.1 2022-05-16 23:55:28 +00:00
Renovate Bot
4468012c9b Update golang.org/x/exp digest to 24438e5 2022-05-16 20:12:02 +00:00
Renovate Bot
0614e146ad Update dependency eslint-plugin-jest to v26.2.2 2022-05-15 01:52:10 +00:00
Renovate Bot
0f3c8dc344 Update dependency eslint-plugin-jest to v26.2.0 2022-05-14 01:46:47 +00:00
Jonas Plum
dfb501f8b9 Remove emitter (#184)
* Remove emitter
2022-05-14 01:08:37 +02:00
Renovate Bot
894e607efb Update module github.com/aws/aws-sdk-go to v1.44.14 2022-05-13 22:52:09 +00:00
Renovate Bot
d40ee1047c Update dependency @koumoul/vjsf to v2.12.0 2022-05-13 14:00:15 +00:00
Renovate Bot
2e176374a2 Update module github.com/aws/aws-sdk-go to v1.44.13 2022-05-12 23:15:17 +00:00
Renovate Bot
3122af7263 Update dependency @types/jest to v27.5.1 2022-05-12 03:41:50 +00:00
Renovate Bot
395d730dbf Update module github.com/coreos/go-oidc/v3 to v3.2.0 2022-05-12 01:19:27 +00:00
Renovate Bot
c9fa3ef456 Update module github.com/aws/aws-sdk-go to v1.44.12 2022-05-11 23:05:42 +00:00
Renovate Bot
fe2a86ba55 Update dependency core-js to v3.22.5 2022-05-11 00:20:20 +00:00
Renovate Bot
30f963a23e Update module github.com/aws/aws-sdk-go to v1.44.11 2022-05-10 21:41:13 +00:00
Renovate Bot
51d7079534 Update typescript-eslint monorepo to v5.23.0 2022-05-10 02:06:52 +00:00
Renovate Bot
b2476b420b Update module github.com/aws/aws-sdk-go to v1.44.10 2022-05-09 23:17:47 +00:00
Renovate Bot
d423943439 Update dependency cypress to v9.6.1 2022-05-09 21:03:11 +00:00
Renovate Bot
41d5994a02 Update dependency luxon to v2.4.0 2022-05-09 10:36:19 +00:00
Renovate Bot
8956fe6033 Update dependency just-kebab-case to v4.0.2 2022-05-09 01:55:44 +00:00
renovate[bot]
246bd17228 Update docker/build-push-action action to v3 (#166)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-07 11:51:16 +02:00
renovate[bot]
2d7d6bff3d Update docker/login-action action to v2 (#167)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-07 11:51:06 +02:00
Renovate Bot
fc8646041e Update module github.com/aws/aws-sdk-go to v1.44.9 2022-05-06 22:50:02 +00:00
Renovate Bot
c6ba604d61 Update module github.com/aws/aws-sdk-go to v1.44.8 2022-05-05 22:20:51 +00:00
renovate[bot]
7916149cf2 Update docker/metadata-action action to v4 (#165)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-05 16:55:42 +02:00
Renovate Bot
d96a090538 Update module github.com/aws/aws-sdk-go to v1.44.7 2022-05-04 22:39:43 +00:00
Renovate Bot
6755602bb5 Update module github.com/aws/aws-sdk-go to v1.44.6 2022-05-04 02:08:09 +00:00
Renovate Bot
5ef778271e Update typescript-eslint monorepo to v5.22.0 2022-05-03 06:32:01 +00:00
Renovate Bot
62f0ac2f38 Update dependency @types/jest to v27.5.0 2022-05-03 03:16:20 +00:00
renovate[bot]
c222674b47 Update module github.com/aws/aws-sdk-go to v1.44.5 (#158)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 23:36:02 +02:00
renovate[bot]
67901ef8dc Update dependency core-js to v3.22.4 (#157)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 23:35:54 +02:00
renovate[bot]
8be35384e1 Update dependency vuetify to v2.6.5 (#156)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 23:35:42 +02:00
Renovate Bot
b0707c0213 Update dependency @types/luxon to v2.3.2 2022-05-02 21:01:48 +00:00
renovate[bot]
58f20c5b1a Update typescript-eslint monorepo to v5 (#151)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 16:44:17 +02:00
renovate[bot]
60c32433a4 Update dependency yaml to v2 (#150)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 16:44:07 +02:00
renovate[bot]
86bc9b779c Update dependency vue-cropperjs to v5 (#148)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 10:50:45 +02:00
renovate[bot]
5d9f790002 Update dependency less-loader to v10 (#143)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 00:35:28 +02:00
renovate[bot]
70169b70aa Update dependency luxon to v2 (#144)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 00:35:14 +02:00
renovate[bot]
65833dfd52 Update dependency just-kebab-case to v4 (#140)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-02 00:22:01 +02:00
renovate[bot]
cefa556f79 Update module github.com/aws/aws-sdk-go to v1.44.4 (#141)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-01 23:46:35 +02:00
renovate[bot]
8489a2d8ff Update dependency less to v4 (#142)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-01 23:46:08 +02:00
renovate[bot]
8a275fb6b9 Update dependency eslint-plugin-jest to v26 (#137)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-01 23:33:29 +02:00
renovate[bot]
4bfdbffbeb Update dependency json-schema-editor-vue to v2 (#139)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-05-01 23:33:09 +02:00
renovate[bot]
2b60558abb Update dependency @vue/eslint-config-typescript to v10 (#133)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 11:37:20 +02:00
renovate[bot]
24b9f54cc5 Update dependency @types/jest to v27 (#132)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 11:37:03 +02:00
renovate[bot]
11f4882ad4 Update dependency @testing-library/vue to v6 (#131)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:40:25 +02:00
renovate[bot]
5f8845d02a Update dependency typescript to v4.6.4 (#130)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:39:23 +02:00
renovate[bot]
43a136137c Update dependency vue-cli-plugin-vuetify to v2.4.8 (#127)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:12:50 +02:00
renovate[bot]
d45ffc5ec4 Update module github.com/aws/aws-sdk-go to v1.44.3 (#128)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:12:34 +02:00
renovate[bot]
6b21a283d4 Update dependency @mdi/font to v6 (#129)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:12:25 +02:00
renovate[bot]
f934516908 Update dependency splitpanes to v2.4.1 (#124)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-29 00:12:17 +02:00
Renovate Bot
1c20ed9552 Update dependency ajv to v8.11.0 2022-04-28 20:04:45 +00:00
renovate[bot]
fbcc3e1943 Update dependency antlr4 to v4.10.1 (#121)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 19:55:26 +02:00
renovate[bot]
1fa4b9f613 Update dependency core-js to v3.22.3 (#123)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 19:55:01 +02:00
renovate[bot]
48b3f877ee Update dependency typescript to v4.6.3 (#125)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 19:54:47 +02:00
renovate[bot]
5bd4a9db2d Update golang.org/x/exp digest to 39d4317 (#126)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 19:54:33 +02:00
Renovate Bot
785a47f2b9 Update dependency @vue/test-utils to v1.3.0 2022-04-28 17:02:30 +00:00
Renovate Bot
28c6136d80 Update dependency @types/prismjs to v1.26.0 2022-04-28 14:56:46 +00:00
Renovate Bot
ebcb6dc4a2 Update vue monorepo 2022-04-28 12:38:43 +00:00
Renovate Bot
48324c7d1d Update module github.com/aws/aws-sdk-go to v1.44.2 2022-04-28 11:45:47 +00:00
Renovate Bot
47303dec84 Update dependency vue-axios to v3.4.1 2022-04-28 05:54:15 +00:00
Renovate Bot
b757e5b7ed Update dependency @types/lodash to v4.14.182 2022-04-28 03:22:42 +00:00
Jonas Plum
89a33bc8f8 Remove renovate schedule 2022-04-28 02:14:25 +02:00
Jonas Plum
f0d9e43414 Automerge npm minor changes 2022-04-28 02:08:19 +02:00
renovate[bot]
c98b11d9e8 Update npm (#62)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-28 02:04:14 +02:00
Jonas Plum
41e4b091f8 Setup cypress (#112) 2022-04-28 01:04:54 +02:00
Renovate Bot
951c968694 Update module github.com/aws/aws-sdk-go to v1.44.1 2022-04-27 01:16:12 +00:00
Renovate Bot
a7dca29c19 Update golang.org/x/exp digest to 3bcf042 2022-04-26 22:41:45 +00:00
Jonas Plum
6f6c615d16 Disable renovate dependency dashboard 2022-04-26 21:25:36 +02:00
Jonas Plum
2227f85db5 Auto merge digest updates 2022-04-26 21:23:58 +02:00
Jonas Plum
c4b32e22ae Fix multiple comments (#109) 2022-04-26 01:09:02 +02:00
renovate[bot]
373cc52c05 Update module github.com/aws/aws-sdk-go to v1.44.0 (#108)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-26 00:31:24 +02:00
Renovate Bot
7c75bff214 Update module github.com/aws/aws-sdk-go to v1.43.45 2022-04-23 00:45:19 +00:00
Renovate Bot
252b4bde2d Update module github.com/aws/aws-sdk-go to v1.43.44 2022-04-21 22:04:21 +00:00
Renovate Bot
0fb30ca51f Update module github.com/aws/aws-sdk-go to v1.43.43 2022-04-21 01:24:23 +00:00
Renovate Bot
ec3774d5aa Update module github.com/tidwall/gjson to v1.14.1 2022-04-20 05:27:58 +00:00
Renovate Bot
dd259d56a4 Update module github.com/aws/aws-sdk-go to v1.43.42 2022-04-20 01:00:46 +00:00
Renovate Bot
352c4ee7a0 Update module github.com/go-chi/cors to v1.2.1 2022-04-19 21:37:44 +00:00
Renovate Bot
3b9d37bbc9 Update module github.com/aws/aws-sdk-go to v1.43.41 2022-04-15 20:55:10 +00:00
Renovate Bot
61e0d40e5a Update module github.com/aws/aws-sdk-go to v1.43.40 2022-04-15 00:00:59 +00:00
renovate[bot]
3865d760ba Update golang.org/x/exp digest to bcd2187 (#94)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-14 23:09:54 +02:00
Renovate Bot
d2e7048d02 Update module github.com/aws/aws-sdk-go to v1.43.39 2022-04-13 21:45:28 +00:00
Renovate Bot
b914b58b23 Update module github.com/aws/aws-sdk-go to v1.43.38 2022-04-12 22:01:09 +00:00
renovate[bot]
2011dae77c Update golang.org/x/oauth2 digest to 9780585 (#88)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-12 06:50:56 +02:00
Renovate Bot
3389389881 Update module github.com/aws/aws-sdk-go to v1.43.37 2022-04-12 03:16:38 +00:00
Renovate Bot
549274083b Update module github.com/aws/aws-sdk-go to v1.43.36 2022-04-08 22:46:50 +00:00
renovate[bot]
0dfe1ddf6a Update golang.org/x/exp digest to 7b9b53b (#82)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-08 17:05:18 +02:00
Renovate Bot
986860e054 Update module github.com/aws/aws-sdk-go to v1.43.35 2022-04-07 23:08:40 +00:00
Renovate Bot
9ea7c8db21 Update module github.com/aws/aws-sdk-go to v1.43.34 2022-04-06 21:57:38 +00:00
Renovate Bot
f8d30fc8a8 Update module github.com/tus/tusd to v1.9.0 2022-04-06 01:42:29 +00:00
Renovate Bot
9a7326cd0a Update module github.com/aws/aws-sdk-go to v1.43.33 2022-04-05 22:46:51 +00:00
renovate[bot]
de61f2385f Update codecov/codecov-action action to v3 (#78)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-04-05 18:19:49 +02:00
Renovate Bot
2a21492f96 Update module github.com/aws/aws-sdk-go to v1.43.32 2022-04-04 22:04:46 +00:00
Renovate Bot
4895e0e8ba Update module github.com/aws/aws-sdk-go to v1.43.31 2022-04-01 23:26:00 +00:00
Renovate Bot
77dba21d63 Update module github.com/aws/aws-sdk-go to v1.43.30 2022-03-31 21:58:29 +00:00
Jonas Plum
dee2827e3f Add gocap check (#73) 2022-03-31 21:43:18 +02:00
Renovate Bot
3c50d4608e Update module github.com/aws/aws-sdk-go to v1.43.29 2022-03-31 02:14:17 +00:00
Renovate Bot
d58182dd9f Update module github.com/blevesearch/bleve/v2 to v2.3.2 2022-03-29 22:14:34 +00:00
renovate[bot]
3c9d98b4ef Update module github.com/tidwall/gjson to v1.14.0 (#61)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:03:27 +02:00
renovate[bot]
b6bb875af4 Update actions/upload-artifact action to v3 (#67)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:02:35 +02:00
renovate[bot]
7627d187c8 Update actions/checkout action to v3 (#64)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:02:23 +02:00
renovate[bot]
a55bba4d0c Update actions/download-artifact action to v3 (#65)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:02:11 +02:00
renovate[bot]
187d3eb8fd Update actions/setup-node action to v3 (#66)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:02:03 +02:00
renovate[bot]
4eb0e95032 Update actions/cache action to v3 (#63)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 22:01:45 +02:00
Renovate Bot
84eb1b07cd Update module github.com/aws/aws-sdk-go to v1.43.28 2022-03-29 19:48:42 +00:00
Renovate Bot
4dcb2e5c7b Update module github.com/arangodb/go-driver to v1.3.1 2022-03-29 19:38:26 +00:00
Renovate Bot
86f6cd72aa Update module github.com/alecthomas/kong to v0.5.0 2022-03-29 19:26:25 +00:00
renovate[bot]
3da58b6eee Update golang.org/x/oauth2 digest to 6242fa9 (#54)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 16:42:31 +02:00
Renovate Bot
19adc38247 Update module github.com/stretchr/testify to v1.7.1 2022-03-29 14:23:01 +00:00
renovate[bot]
f15ce29d7e Update github.com/antlr/antlr4/runtime/Go/antlr digest to 97c793e (#51)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 15:58:03 +02:00
renovate[bot]
7bf9a03eec Update golang.org/x/exp digest to 053ad81 (#52)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-03-29 15:57:48 +02:00
Jonas Plum
3fe863a735 Add codecov test (#50) 2022-03-29 15:55:53 +02:00
Jonas Plum
9c8ed2a089 Setup renovate (#49) 2022-03-29 15:49:14 +02:00
Jonas Plum
2158899983 Improve user info (#47) 2022-03-20 12:57:15 +01:00
Jonas Plum
68618d2bdb Setup CI cache (#46) 2022-03-20 03:40:42 +01:00
Jonas Plum
2bad1f5f28 Migrate to Go 1.18 (#45)
* Migrate to Go 1.18 and add linters
2022-03-20 03:17:18 +01:00
Jonas Plum
03a4806d45 Fix timeformat (#44) 2022-03-19 14:26:36 +01:00
Jonas Plum
e6baead486 Fix routing (#43) 2022-03-19 13:41:34 +01:00
Jonas Plum
3618f9784d Fix home screen (#42) 2022-03-16 00:27:46 +01:00
825 changed files with 54060 additions and 91038 deletions

11
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
coverage:
status:
project:
default:
threshold: 5%
patch: off
comment:
layout: diff
parsers:
go:
partials_as_hits: true

View File

@@ -4,77 +4,126 @@ on:
pull_request:
release: { types: [ published ] }
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
name: Test
generate-go:
name: Generate Go
runs-on: ubuntu-latest
env: { GIN_MODE: test }
steps:
- uses: actions/setup-go@v2
with: { go-version: '1.17' }
- uses: actions/setup-node@v2
with: { node-version: '14' }
- uses: actions/checkout@v2
- run: |
mkdir -p ui/dist/img
touch ui/dist/index.html ui/dist/favicon.ico ui/dist/manifest.json ui/dist/img/fake.png
- run: docker-compose up -d
working-directory: dev
- name: Install ArangoDB
run: |
curl -OL https://download.arangodb.com/arangodb38/DEBIAN/Release.key
sudo apt-key add Release.key
sudo apt-add-repository 'deb https://download.arangodb.com/arangodb38/DEBIAN/ /'
sudo apt-get update -y && sudo apt-get -y install arangodb3
- run: go test -coverprofile=cover.out -coverpkg=./... ./...
- run: go tool cover -func=cover.out
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- run: make install-golangci-lint generate-go
- run: git diff --exit-code
build-npm:
name: Build npm
generate-ui:
name: Generate UI
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v2
with: { node-version: '14' }
- uses: actions/checkout@v2
- run: yarn install && yarn build
working-directory: ui
- uses: actions/upload-artifact@v2
with: { name: ui, path: ui/dist, retention-days: 1 }
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: make install-ui generate-ui
- run: git diff --exit-code
build:
if: github.event_name != 'pull_request'
name: Build
fmt-go:
name: Fmt Go
runs-on: ubuntu-latest
needs: [ build-npm, test ]
steps:
- uses: actions/setup-go@v2
with: { go-version: '1.17' }
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with: { name: ui, path: ui/dist }
- run: go build -o catalyst ./cmd/catalyst/.
- uses: docker/login-action@v1
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- run: make install-golangci-lint fmt-go
- run: git diff --exit-code
fmt-ui:
name: Fmt UI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: make install-ui fmt-ui
lint-go:
name: Lint Go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- run: make install-golangci-lint lint-go
lint-ui:
name: Lint UI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: make install-ui lint-ui
build-ui:
name: Build UI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: make install-ui build-ui
test-go:
name: Test Go
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- run: make test-coverage
- uses: codecov/codecov-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Version
if: ${{ github.ref != '' }}
files: ./coverage.out
token: ${{ secrets.CODECOV_TOKEN }}
test-ui:
name: Test UI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- uses: oven-sh/setup-bun@v1
- run: make install-ui test-ui
test-playwright:
name: Test Playwright
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- uses: oven-sh/setup-bun@v1
- run: make install-ui build-ui install-playwright test-playwright
list-upgrade-test-folders:
name: List Upgrade Test Folders
runs-on: ubuntu-latest
outputs:
folders: ${{ steps.set-dirs.outputs.matrix }}
steps:
- uses: actions/checkout@v4
- id: set-dirs
run: |
echo ${{ github.ref_name }}
echo ${{ github.ref_name }} > VERSION
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
FOLDERS=$(ls -d ./testing/data/*/ | xargs -n 1 basename | jq -R . | jq -c -s .)
echo "matrix=$FOLDERS" >> $GITHUB_OUTPUT
test-upgrade-playwright:
name: Test Playwright Upgrade
needs: list-upgrade-test-folders
runs-on: ubuntu-latest
strategy:
matrix:
folder: ${{ fromJson(needs.list-upgrade-test-folders.outputs.folders) }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- uses: oven-sh/setup-bun@v1
- run: mkdir -p catalyst_data
- run: cp testing/data/${{ matrix.folder }}/data.db catalyst_data/data.db
- run: make install-ui build-ui install-playwright test-playwright

40
.github/workflows/goreleaser.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: goreleaser
on:
push:
tags:
- "*"
permissions:
contents: write
id-token: write
packages: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with: { go-version: '1.25' }
- uses: oven-sh/setup-bun@v1
- run: make install-ui build-ui
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: "securitybrewery"
password: ${{ secrets.GITHUB_TOKEN }}
- name: Run GoReleaser
run: |
docker run --rm --privileged \
-v `pwd`:/go/src/github.com/SecurityBrewery/catalyst \
-v /var/run/docker.sock:/var/run/docker.sock \
-w /go/src/github.com/SecurityBrewery/catalyst \
-e CGO_ENABLED=1 \
-e GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} \
ghcr.io/goreleaser/goreleaser-cross:v1.25.1 \
release --clean

View File

@@ -0,0 +1,25 @@
name: Semantic Pull Request
on:
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened
permissions:
pull-requests: read
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
with:
scopes: |
deps
subjectPattern: ^(?![A-Z]).+$
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

123
.gitignore vendored
View File

@@ -1,86 +1,47 @@
.idea
.antlr
.DS_Store
uploads
gen
*.bleve
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
venv/
.venv/
.python-version
.pytest_cache
# Translations
*.mo
*.pot
# Django stuff:
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Sphinx documentation
docs/_build/
# PyBuilder
target/
#Ipython Notebook
.ipynb_checkpoints
# npm
wwwroot/*.js
typings
dist
node_modules
.DS_Store
dist-ssr
coverage
*.local
profile.cov
/cypress/videos/
/cypress/screenshots/
generated/caql/parser/*.interp
generated/caql/parser/*.tokens
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo
bin
dist
pb_data
catalyst
catalyst_data
# ignore changes, needs to be disabled when adding new upgrade tests
testing/**/*.db
testing/**/*.db-shm
testing/**/*.db-wal
coverage.out
test-results
playwright/playwright-report
openapitools.json

59
.golangci.yml Normal file
View File

@@ -0,0 +1,59 @@
version: "2"
run:
go: "1.22"
linters:
default: all
disable:
- depguard
- dupl
- err113
- exhaustruct
- funcorder
- funlen
- gochecknoglobals
- godox
- gomoddirectives
- ireturn
- lll
- maintidx
- mnd
- noinlineerr
- nonamedreturns
- perfsprint
- prealloc
- tagalign
- tagliatelle
- testpackage
- unparam
- varnamelen
- wrapcheck
- wsl
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/SecurityBrewery/catalyst)
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

110
.goreleaser.yaml Normal file
View File

@@ -0,0 +1,110 @@
version: 2
before:
hooks:
- go mod tidy
builds:
- id: darwin-amd64
main: ./
binary: catalyst
goos:
- darwin
goarch:
- amd64
env:
- CGO_ENABLED=1
- CC=o64-clang
- CXX=o64-clang++
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: linux-arm64
main: ./
binary: catalyst
goos:
- linux
goarch:
- arm64
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc
- CXX=aarch64-linux-gnu-g++
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: linux-amd64
main: ./
binary: catalyst
goos:
- linux
goarch:
- amd64
env:
- CGO_ENABLED=1
- CC=x86_64-linux-gnu-gcc
- CXX=x86_64-linux-gnu-g++
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: windows-amd64
main: ./
binary: catalyst
goos:
- windows
goarch:
- amd64
env:
- CGO_ENABLED=1
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
- id: windows-arm64
main: ./
binary: catalyst
goos:
- windows
goarch:
- arm64
env:
- CGO_ENABLED=1
- CC=/llvm-mingw/bin/aarch64-w64-mingw32-gcc
- CXX=/llvm-mingw/bin/aarch64-w64-mingw32-g++
flags:
- -mod=readonly
ldflags:
- -s -w -X main.version={{.Version}}
dockers:
- ids: [ linux-amd64 ]
dockerfile: docker/Dockerfile
image_templates:
- "ghcr.io/securitybrewery/catalyst:main"
- "{{if not .Prerelease}}ghcr.io/securitybrewery/catalyst:latest{{end}}"
- "ghcr.io/securitybrewery/catalyst:{{.Tag}}"
extra_files:
- docker/entrypoint.sh
archives:
- formats: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
formats: zip
changelog:
sort: asc

View File

@@ -1,12 +0,0 @@
FROM ubuntu:18.04
RUN apt-get update -y && apt-get -y install curl gnupg2 software-properties-common
RUN curl -OL https://download.arangodb.com/arangodb34/DEBIAN/Release.key
RUN apt-key add Release.key
RUN apt-add-repository 'deb https://download.arangodb.com/arangodb34/DEBIAN/ /'
RUN apt-get update -y && apt-get -y install arangodb3
COPY catalyst /app/catalyst
CMD /app/catalyst
EXPOSE 8000

228
Makefile Normal file
View File

@@ -0,0 +1,228 @@
#########
## install
#########
.PHONY: install-golangci-lint
install-golangci-lint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v2.4.0
.PHONY: install-ui
install-ui:
cd ui && bun install
.PHONY: install-playwright
install-playwright:
cd ui && bun install && bun install:e2e
#########
## fmt
#########
.PHONY: fmt-go
fmt-go:
go mod tidy
gofmt -r 'interface{} -> any' -w **/*.go
golangci-lint fmt ./...
.PHONY: fmt-ui
fmt-ui:
cd ui && bun format
.PHONY: fmt
fmt: fmt-go fmt-ui
#########
## fix
#########
.PHONY: fix-go
fix-go:
golangci-lint run --fix ./...
.PHONY: fix-ui
fix-ui:
cd ui && bun lint --fix
.PHONY: fix
fix: fix-go fix-ui
#########
## lint
#########
.PHONY: lint-go
lint-go:
golangci-lint version
golangci-lint run ./...
.PHONY: lint-ui
lint-ui:
cd ui && bun lint --max-warnings 0
.PHONY: lint
lint: lint-go lint-ui
#########
## test
#########
.PHONY: test-go
test-go:
go test ./...
.PHONY: test-ui
test-ui:
cd ui && bun test src
.PHONY: test-short
test-short: test-go test-ui
.PHONY: test-playwright
test-playwright:
cd ui && bun test:e2e
.PHONY: test-demo-playwright
test-demo-playwright:
cd ui && bun test:e2e:demo
.PHONY: test-playwright-ui
test-playwright-ui:
cd ui && bun test:e2e:ui
.PHONY: test-upgrade-playwright
test-upgrade-playwright:
./testing/test_all.sh
.PHONY: test
test: test-short test-playwright test-upgrade-playwright
.PHONY: test-coverage
test-coverage:
go test -coverpkg=./... -coverprofile=coverage.out -count 1 ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
##########
## build
##########
.PHONY: build-ui
build-ui:
cd ui && bun build-only
touch ui/dist/.keep
.PHONY: build
build: build-ui
go build -o catalyst .
.PHONY: build-linux
build-linux: build-ui
GOOS=linux GOARCH=amd64 go build -o catalyst .
.PHONY: docker
docker: build-linux
docker build -f docker/Dockerfile -t catalyst .
############
## run
############
.PHONY: reset_data
reset_data:
rm -rf catalyst_data
.PHONY: copy_existing_data
copy_existing_data: reset_data
mkdir -p catalyst_data
cp testing/data/v0.14.1/data.db catalyst_data/data.db
.PHONY: dev
dev: reset_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . fake-data
go run . serve --app-url http://localhost:8090 --flags dev
.PHONY: dev-proxy-ui
dev-proxy-ui: reset_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . fake-data
UI_DEVSERVER=http://localhost:3000 go run . serve --app-url http://localhost:8090 --flags dev --flags demo
.PHONY: dev-upgrade-proxy-ui
dev-upgrade-proxy-ui: copy_existing_data
go run . admin create admin@catalyst-soar.com 1234567890
UI_DEVSERVER=http://localhost:3000 go run . serve --app-url http://localhost:8090 --flags dev
.PHONY: dev-10000-proxy-ui
dev-10000-proxy-ui: reset_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . fake-data --users 87 --tickets 12425
UI_DEVSERVER=http://localhost:3000 go run . serve --app-url http://localhost:8090 --flags dev
.PHONY: dev-upgrade
dev-upgrade: copy_existing_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . serve --app-url http://localhost:8090 --flags dev
.PHONY: dev-demo
dev-demo: copy_existing_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . serve --app-url http://localhost:8090 --flags demo
.PHONY: dev-10000
dev-10000: reset_data
go run . admin create admin@catalyst-soar.com 1234567890
go run . fake-data --users 87 --tickets 12425
go run . serve --app-url http://localhost:8090 --flags dev
.PHONY: default-data
default-data:
rm -rf catalyst_data
go run . default-data
.PHONY: serve-ui
serve-ui:
cd ui && bun dev --port 3000
#########
## generate
#########
.PHONY: sqlc
sqlc:
rm -rf app/database/sqlc
cd app/database && go tool sqlc generate
sed -i.bak 's/Queries/ReadQueries/g' app/database/sqlc/read.sql.go
rm -f app/database/sqlc/read.sql.go.bak
sed -i.bak 's/Queries/WriteQueries/g' app/database/sqlc/write.sql.go
rm -f app/database/sqlc/write.sql.go.bak
cp app/database/sqlc.db.go.tmpl app/database/sqlc/db.go
.PHONY: openapi-go
openapi-go:
go tool oapi-codegen --config=app/openapi/config.yml openapi.yml
.PHONY: openapi-ui
openapi-ui:
rm -rf ui/src/client
cd ui && bun generate
.PHONY: openapi
openapi: openapi-go openapi-ui
.PHONY: generate-go
generate-go: openapi-go sqlc fmt-go
.PHONY: generate-ui
generate-ui: openapi-ui fmt-ui
.PHONY: generate
generate: generate-go generate-ui
#########
## screenshots
#########
.PHONY: screenshots
screenshots:
bash ui/screenshot.sh

16
NOTICE
View File

@@ -1,16 +0,0 @@
The following components are included in this product:
Badgerodon Collections
https://github.com/badgerodon/collections
Copyright (c) 2012 Caleb Doxsey
Licensed under the MIT License
go-toposort
https://github.com/philopon/go-toposort
Copyright (c) 2017 Hirotomo Moriwaki
Licensed under the MIT License
The Go programming language
https://go.dev/
Copyright (c) 2009 The Go Authors
See https://go.dev/LICENSE for license details.

108
README.md
View File

@@ -1,97 +1,87 @@
<h1 align="center">
<img width="30" alt="Screenshot of the playbook part of a ticket" src="ui/public/flask_white.svg" />
<picture>
<source media="(prefers-color-scheme: dark)" srcset="ui/src/assets/flask_white.svg">
<img width="30" alt="Shows an illustrated sun in light color mode and a moon with stars in dark color mode." src="ui/src/assets/flask.svg">
</picture>
Catalyst</h1>
<h3 align="center">Speed up your reactions</h3>
<h4 align="center">
<a href="https://catalyst-soar.com">Website</a>
<a href="https://catalyst.security-brewery.com/">Website</a>
-
<a href="https://catalyst-soar.com/docs/category/catalyst-handbook">The Catalyst Handbook (Documentation)</a>
<a href="https://catalyst.security-brewery.com/docs/category/catalyst-handbook">The Catalyst Handbook (Documentation)</a>
-
<a href="https://try.catalyst-soar.com">Try online</a>
</h4>
<h4 align="center">
<a href="https://twitter.com/securitybrewery">Twitter</a>
-
<a href="https://discord.gg/nrmpveWvZX">Discord</a>
<a href="https://try.catalyst.security-brewery.com/">Demo</a>
</h4>
Catalyst is an incident response platform or SOAR (Security Orchestration, Automation and Response) system. It can help
you to automate your alert handling and incident response procedures.
Catalyst is an incident response platform.
It can help to automate your alert handling and incident response procedures.
## Features
### Ticket (Alert & Incident) Management
![Screenshot of a ticket](docs/screenshots/ticket.png)
Tickets are the core of Catalyst. They represent alerts, incidents, forensics
investigations, threat hunts or any other event you want to handle in your
organisation.
### Ticket Templates
Tickets are the core of Catalyst.
They represent alerts, incidents, forensics investigations,
threat hunts or any other event you want to handle in your organisation.
<center>
<img width="400" alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/details.png" />
<a href="/docs/screenshots/ticket.png">
<img alt="Screenshot of a ticket" src="/docs/screenshots/ticket.png" />
</a>
</center>
Templates define the custom information for tickets. The core information for
tickets like title, creation date or closing status is kept quite minimal and other
information like criticality, description or MITRE ATT&CK information can be
added individually.
### Tasks
### Conditional Custom Fields
Tasks are the smallest unit of work in Catalyst. They can be assigned to users and have a status.
Tasks can be used to document the progress of an investigation or to assign work to different users.
<center>
<img width="400" alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/conditional_custom_field_a.png" />
<img width="400" alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/conditional_custom_field_b.png" />
<a href="/docs/screenshots/tasks.png">
<img alt="Screenshot of the tasks part of a ticket" src="/docs/screenshots/tasks.png" />
</a>
</center>
Custom Fields can be dependent on each other. So if you, for example choose
"malware" as an incident type a custom field ask you to define it further as
ransomware, worm, etc. which a "phishing" incident would ask for the number
of received mails in that campaign.
### Reactions
### Playbooks
Reactions are a way to automate Catalyst.
Each reaction is composed of a trigger and an action.
The trigger listens for events and the action is executed when the trigger is activated.
There are triggers for HTTP/Webhooks and Collection Hooks and actions for Python and HTTP/Webhooks.
<center>
<img alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/phishing_playbook.png" />
<a href="/docs/screenshots/reactions.png">
<img alt="Screenshot of the reactions" src="/docs/screenshots/reactions.png" />
</a>
</center>
Playbooks represent processes that can be attached to tickets. Playbooks can
contain manual and automated tasks. Complex workflows with different workflow
branches, parallel tasks and task dependencies can be modeled.
### Timelines
### Automations
Timelines are used to document the progress of an investigation.
They can be used to document the steps taken during an investigation, the findings or the results of the investigation.
### Dashboards
Catalyst comes with a dashboard that presents the most important information at a glance.
<center>
<img alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/script.png" />
<a href="/docs/screenshots/dashboard.png">
<img alt="Screenshot of the dashboard" src="/docs/screenshots/dashboard.png" />
</a>
</center>
Automations are scripts that automate tasks or enrich artifacts. Automations are
run in their own Docker containers. This enables them to be created in different
scripting languages and run securely in their own environment.
### Ticket Types
### Users
Templates define the custom information for tickets.
The core information for tickets like title, creation date or closing status is kept quite minimal
and other information like criticality, description or MITRE ATT&CK information can be added individually.
<center>
<img alt="Screenshot of the playbook part of a ticket" src="docs/screenshots/roles.png" />
</center>
### Custom Fields
Catalyst has two different types of users, normal users accessing the platform
via OIDC authentication and API keys for external script. A
fine-grained access model is available for both types and allows to define
possible actions for each user.
Custom fields can be added to tickets to store additional information.
They can be used to store information like the affected system, the attacker's IP address or the type of malware.
Custom fields can be added to ticket types and are then available for all tickets of this type.
## License
### More
Copyright (c) 2021-present Jonas Plum
Portions of this software are licensed as follows:
* All third party components incorporated into Catalyst are licensed under the
original license provided by the owner of the applicable component. Those
files contain a license notice on top of the file and are listed in the
[NOTICE](NOTICE) file.
* Content outside the above-mentioned files above is
available under the [GNU Affero General Public License v3.0](LICENSE).
Catalyst supports a lot more features like: Links, Files, or Comments on tickets.

View File

@@ -1 +0,0 @@
0.0.0-dev

122
admin.go Normal file
View File

@@ -0,0 +1,122 @@
package main
import (
"context"
"errors"
"fmt"
"log/slog"
"github.com/urfave/cli/v3"
"github.com/SecurityBrewery/catalyst/app/auth/password"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
func adminCreate(ctx context.Context, command *cli.Command) error {
catalyst, cleanup, err := setup(ctx, command)
if err != nil {
return fmt.Errorf("failed to setup catalyst: %w", err)
}
defer cleanup()
if command.Args().Len() != 2 {
return errors.New("usage: catalyst admin create <email> <password>")
}
name, email := command.Args().Get(0), command.Args().Get(0)
pw := command.Args().Get(1)
passwordHash, tokenKey, err := password.Hash(pw)
if err != nil {
return errors.New("failed to hash password: " + err.Error())
}
user, err := catalyst.Queries.CreateUser(ctx, sqlc.CreateUserParams{
Name: &name,
Email: &email,
Username: "admin",
PasswordHash: passwordHash,
TokenKey: tokenKey,
Active: true,
})
if err != nil {
return err
}
if err := catalyst.Queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
UserID: user.ID,
GroupID: "admin",
}); err != nil {
return err
}
slog.InfoContext(ctx, "Creating admin", "id", user.ID, "email", user.Email)
return nil
}
func adminSetPassword(ctx context.Context, command *cli.Command) error {
catalyst, cleanup, err := setup(ctx, command)
if err != nil {
return fmt.Errorf("failed to setup catalyst: %w", err)
}
defer cleanup()
if command.Args().Len() != 2 {
return errors.New("usage: catalyst admin set-password <email> <password>")
}
mail := command.Args().Get(0)
user, err := catalyst.Queries.UserByEmail(ctx, &mail)
if err != nil {
return err
}
passwordHash, tokenKey, err := password.Hash(command.Args().Get(1))
if err != nil {
return errors.New("failed to hash password: " + err.Error())
}
if _, err := catalyst.Queries.UpdateUser(ctx, sqlc.UpdateUserParams{
ID: user.ID,
PasswordHash: &passwordHash,
TokenKey: &tokenKey,
}); err != nil {
return err
}
slog.InfoContext(ctx, "Setting password for admin", "id", user.ID, "email", user.Email)
return nil
}
func adminDelete(ctx context.Context, command *cli.Command) error {
catalyst, cleanup, err := setup(ctx, command)
if err != nil {
return fmt.Errorf("failed to setup catalyst: %w", err)
}
defer cleanup()
if command.Args().Len() != 1 {
return errors.New("usage: catalyst admin delete <email>")
}
mail := command.Args().Get(0)
user, err := catalyst.Queries.UserByEmail(ctx, &mail)
if err != nil {
return err
}
if err := catalyst.Queries.DeleteUser(ctx, user.ID); err != nil {
return err
}
slog.InfoContext(ctx, "Deleted admin", "id", user.ID, "email", mail)
return nil
}

75
app/app.go Normal file
View File

@@ -0,0 +1,75 @@
package app
import (
"context"
"fmt"
"net/http"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/hook"
"github.com/SecurityBrewery/catalyst/app/mail"
"github.com/SecurityBrewery/catalyst/app/migration"
"github.com/SecurityBrewery/catalyst/app/reaction"
"github.com/SecurityBrewery/catalyst/app/reaction/schedule"
"github.com/SecurityBrewery/catalyst/app/router"
"github.com/SecurityBrewery/catalyst/app/service"
"github.com/SecurityBrewery/catalyst/app/upload"
"github.com/SecurityBrewery/catalyst/app/webhook"
)
type App struct {
Queries *sqlc.Queries
Hooks *hook.Hooks
router http.Handler
}
func New(ctx context.Context, dir string) (*App, func(), error) {
uploader, err := upload.New(dir)
if err != nil {
return nil, nil, fmt.Errorf("failed to create uploader: %w", err)
}
queries, cleanup, err := database.DB(ctx, dir)
if err != nil {
return nil, nil, fmt.Errorf("failed to connect to database: %w", err)
}
if err := migration.Apply(ctx, queries, dir, uploader); err != nil {
return nil, nil, fmt.Errorf("failed to migrate database: %w", err)
}
mailer := mail.New(queries)
scheduler, err := schedule.New(ctx, queries)
if err != nil {
return nil, cleanup, fmt.Errorf("failed to create scheduler: %w", err)
}
hooks := hook.NewHooks()
service := service.New(queries, hooks, uploader, scheduler)
router, err := router.New(service, queries, uploader, mailer)
if err != nil {
return nil, nil, fmt.Errorf("failed to create router: %w", err)
}
if err := reaction.BindHooks(hooks, router, queries, false); err != nil {
return nil, nil, err
}
webhook.BindHooks(hooks, queries)
app := &App{
Queries: queries,
Hooks: hooks,
router: router,
}
return app, cleanup, nil
}
func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.router.ServeHTTP(w, r)
}

16
app/auth/errorjson.go Normal file
View File

@@ -0,0 +1,16 @@
package auth
import (
"fmt"
"net/http"
)
func unauthorizedJSON(w http.ResponseWriter, msg string) {
errorJSON(w, http.StatusUnauthorized, msg)
}
func errorJSON(w http.ResponseWriter, status int, msg string) {
w.WriteHeader(status)
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
_, _ = fmt.Fprintf(w, `{"status": %d, "error": %q, "message": %q}`, status, http.StatusText(status), msg)
}

158
app/auth/middleware.go Normal file
View File

@@ -0,0 +1,158 @@
package auth
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
"slices"
"strings"
strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"
"github.com/SecurityBrewery/catalyst/app/auth/usercontext"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/openapi"
)
const bearerPrefix = "Bearer "
func Middleware(queries *sqlc.Queries) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/config" {
next.ServeHTTP(w, r)
return
}
authorizationHeader := r.Header.Get("Authorization")
bearerToken := strings.TrimPrefix(authorizationHeader, bearerPrefix)
user, claims, err := verifyAccessToken(r.Context(), bearerToken, queries)
if err != nil {
slog.ErrorContext(r.Context(), "invalid bearer token", "error", err)
unauthorizedJSON(w, "invalid bearer token")
return
}
scopes, err := scopes(claims)
if err != nil {
slog.ErrorContext(r.Context(), "failed to get scopes from token", "error", err)
unauthorizedJSON(w, "failed to get scopes")
return
}
// Set the user in the context
r = usercontext.UserRequest(r, user)
r = usercontext.PermissionRequest(r, scopes)
next.ServeHTTP(w, r)
})
}
}
func ValidateFileScopes(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requiredScopes := []string{"file:read"}
if slices.Contains([]string{http.MethodPost, http.MethodPatch, http.MethodPut, http.MethodDelete}, r.Method) {
requiredScopes = []string{"file:write"}
}
if err := validateScopes(r.Context(), requiredScopes); err != nil {
slog.ErrorContext(r.Context(), "failed to validate scopes", "error", err)
unauthorizedJSON(w, "missing required scopes")
return
}
next.ServeHTTP(w, r)
})
}
func ValidateScopesStrict(next strictnethttp.StrictHTTPHandlerFunc, _ string) strictnethttp.StrictHTTPHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, request any) (response any, err error) {
requiredScopes, err := requiredScopes(ctx)
if err != nil {
slog.ErrorContext(ctx, "failed to get required scopes", "error", err)
unauthorizedJSON(w, "failed to get required scopes")
return nil, fmt.Errorf("failed to get required scopes: %w", err)
}
if err := validateScopes(ctx, requiredScopes); err != nil {
slog.ErrorContext(ctx, "failed to validate scopes", "error", err)
unauthorizedJSON(w, "missing required scopes")
return nil, fmt.Errorf("missing required scopes: %w", err)
}
return next(ctx, w, r, request)
}
}
func LogError(next strictnethttp.StrictHTTPHandlerFunc, _ string) strictnethttp.StrictHTTPHandlerFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, request any) (response any, err error) {
re, err := next(ctx, w, r, request)
if err != nil {
if err.Error() == "context canceled" {
// This is a common error when the request is canceled, e.g., by the client.
// We can ignore this error as it does not indicate a problem with the handler.
return re, nil
}
slog.ErrorContext(ctx, "handler error", "error", err, "method", r.Method, "path", r.URL.Path)
}
return re, err
}
}
func validateScopes(ctx context.Context, requiredScopes []string) error {
if len(requiredScopes) > 0 {
permissions, ok := usercontext.PermissionFromContext(ctx)
if !ok {
return errors.New("missing permissions")
}
if !hasScope(permissions, requiredScopes) {
return fmt.Errorf("missing required scopes: %v", requiredScopes)
}
}
return nil
}
func requiredScopes(ctx context.Context) ([]string, error) {
requiredScopesValue := ctx.Value(openapi.OAuth2Scopes)
if requiredScopesValue == nil {
return nil, nil
}
requiredScopes, ok := requiredScopesValue.([]string)
if !ok {
return nil, fmt.Errorf("invalid required scopes type: %T", requiredScopesValue)
}
return requiredScopes, nil
}
func hasScope(scopes []string, requiredScopes []string) bool {
if slices.Contains(scopes, "admin") {
// If the user has admin scope, they can access everything
return true
}
for _, s := range requiredScopes {
if !slices.Contains(scopes, s) {
return false
}
}
return true
}

188
app/auth/middleware_test.go Normal file
View File

@@ -0,0 +1,188 @@
package auth
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/SecurityBrewery/catalyst/app/auth/usercontext"
"github.com/SecurityBrewery/catalyst/app/openapi"
)
func mockHandler(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`{"message":"OK"}`))
}
func TestService_ValidateScopes(t *testing.T) {
t.Parallel()
type args struct {
requiredScopes []string
permissions []string
next http.HandlerFunc
}
tests := []struct {
name string
args args
want httptest.ResponseRecorder
}{
{
name: "no scopes",
args: args{
requiredScopes: []string{"user:read"},
permissions: []string{},
next: mockHandler,
},
want: httptest.ResponseRecorder{
Code: http.StatusUnauthorized,
Body: bytes.NewBufferString(`{"error": "Unauthorized", "message": "missing required scopes", "status": 401}`),
},
},
{
name: "insufficient scopes",
args: args{
requiredScopes: []string{"user:write"},
permissions: []string{"user:read"},
next: mockHandler,
},
want: httptest.ResponseRecorder{
Code: http.StatusUnauthorized,
Body: bytes.NewBufferString(`{"error": "Unauthorized", "message": "missing required scopes", "status": 401}`),
},
},
{
name: "sufficient scopes",
args: args{
requiredScopes: []string{"user:read"},
permissions: []string{"user:read", "user:write"},
next: mockHandler,
},
want: httptest.ResponseRecorder{
Code: http.StatusOK,
Body: bytes.NewBufferString(`{"message":"OK"}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
handler := ValidateScopesStrict(func(_ context.Context, w http.ResponseWriter, r *http.Request, _ any) (response any, err error) {
tt.args.next(w, r)
return w, nil
}, "")
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
//nolint: staticcheck
r = r.WithContext(context.WithValue(r.Context(), openapi.OAuth2Scopes, tt.args.requiredScopes))
r = usercontext.PermissionRequest(r, tt.args.permissions)
if _, err := handler(r.Context(), w, r, r); err != nil {
return
}
assert.Equal(t, tt.want.Code, w.Code, "response code should match expected value")
assert.JSONEq(t, tt.want.Body.String(), w.Body.String(), "response body should match expected value")
})
}
}
func Test_hasScope(t *testing.T) {
t.Parallel()
type args struct {
scopes []string
requiredScopes []string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "no scopes",
args: args{
scopes: []string{},
requiredScopes: []string{"user:read"},
},
want: false,
},
{
name: "missing required scope",
args: args{
scopes: []string{"user:read"},
requiredScopes: []string{"user:write"},
},
},
{
name: "has required scope",
args: args{
scopes: []string{"user:read", "user:write"},
requiredScopes: []string{"user:read"},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
assert.Equalf(t, tt.want, hasScope(tt.args.scopes, tt.args.requiredScopes), "hasScope(%v, %v)", tt.args.scopes, tt.args.requiredScopes)
})
}
}
func Test_requiredScopes(t *testing.T) {
t.Parallel()
type args struct {
r *http.Request
}
tests := []struct {
name string
args args
want []string
wantErr assert.ErrorAssertionFunc
}{
{
name: "no required scopes",
args: args{
r: httptest.NewRequest(http.MethodGet, "/", nil),
},
want: nil,
wantErr: assert.NoError,
},
{
name: "valid required scopes",
args: args{
//nolint: staticcheck
r: httptest.NewRequest(http.MethodGet, "/", nil).WithContext(context.WithValue(t.Context(), openapi.OAuth2Scopes, []string{"user:read", "user:write"})),
},
want: []string{"user:read", "user:write"},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := requiredScopes(tt.args.r.Context())
if !tt.wantErr(t, err, fmt.Sprintf("requiredScopes(%v)", tt.args.r)) {
return
}
assert.Equalf(t, tt.want, got, "requiredScopes(%v)", tt.args.r)
})
}
}

View File

@@ -0,0 +1,32 @@
package password
import (
"crypto/rand"
"encoding/base64"
"fmt"
"golang.org/x/crypto/bcrypt"
)
func Hash(password string) (hashedPassword, tokenKey string, err error) {
hashedPasswordB, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", "", fmt.Errorf("failed to hash password: %w", err)
}
tokenKey, err = GenerateTokenKey()
if err != nil {
return "", "", err
}
return string(hashedPasswordB), tokenKey, nil
}
func GenerateTokenKey() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}

View File

@@ -0,0 +1,67 @@
package password
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
func TestHash(t *testing.T) {
t.Parallel()
type args struct {
password string
}
tests := []struct {
name string
args args
wantErr require.ErrorAssertionFunc
}{
{
name: "Hash valid password",
args: args{
password: "securePassword123!",
},
wantErr: require.NoError,
},
{
name: "Long password",
args: args{
password: strings.Repeat("a", 75),
},
wantErr: require.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
gotHashedPassword, gotTokenKey, err := Hash(tt.args.password)
tt.wantErr(t, err, "Hash() should not return an error")
if err != nil {
return
}
assert.NotEmpty(t, gotHashedPassword, "Hash() gotHashedPassword should not be empty")
assert.NotEmpty(t, gotTokenKey, "Hash() gotTokenKey should not be empty")
require.NoError(t, bcrypt.CompareHashAndPassword([]byte(gotHashedPassword), []byte(tt.args.password)), "Hash() hashed password does not match original password")
assert.GreaterOrEqual(t, len(gotTokenKey), 43, "Hash() gotTokenKey should be at least 43 characters long")
})
}
}
func TestGenerateTokenKey(t *testing.T) {
t.Parallel()
tokenKey, err := GenerateTokenKey()
require.NoError(t, err)
assert.NotEmpty(t, tokenKey, "GenerateTokenKey() tokenKey should not be empty")
assert.GreaterOrEqual(t, len(tokenKey), 43, "GenerateTokenKey() tokenKey should be at least 43 characters long")
}

73
app/auth/permission.go Normal file
View File

@@ -0,0 +1,73 @@
package auth
import (
"context"
"encoding/json"
"log/slog"
)
var (
TicketReadPermission = "ticket:read"
TicketWritePermission = "ticket:write"
FileReadPermission = "file:read"
FileWritePermission = "file:write"
TypeReadPermission = "type:read"
TypeWritePermission = "type:write"
UserReadPermission = "user:read"
UserWritePermission = "user:write"
GroupReadPermission = "group:read"
GroupWritePermission = "group:write"
ReactionReadPermission = "reaction:read"
ReactionWritePermission = "reaction:write"
WebhookReadPermission = "webhook:read"
WebhookWritePermission = "webhook:write"
SettingsReadPermission = "settings:read"
SettingsWritePermission = "settings:write"
)
func All() []string {
return []string{
TicketReadPermission,
TicketWritePermission,
FileReadPermission,
FileWritePermission,
TypeReadPermission,
TypeWritePermission,
UserReadPermission,
UserWritePermission,
GroupReadPermission,
GroupWritePermission,
ReactionReadPermission,
ReactionWritePermission,
WebhookReadPermission,
WebhookWritePermission,
SettingsReadPermission,
SettingsWritePermission,
}
}
func FromJSONArray(ctx context.Context, permissions string) []string {
var result []string
if err := json.Unmarshal([]byte(permissions), &result); err != nil {
slog.ErrorContext(ctx, "Failed to unmarshal permissions", "error", err)
return nil
}
return result
}
func ToJSONArray(ctx context.Context, permissions []string) string {
if len(permissions) == 0 {
return "[]"
}
data, err := json.Marshal(permissions)
if err != nil {
slog.ErrorContext(ctx, "Failed to marshal permissions", "error", err)
return "[]"
}
return string(data)
}

View File

@@ -0,0 +1,84 @@
package auth
import (
"reflect"
"testing"
)
func TestFromJSONArray(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input string
want []string
shouldError bool
}{
{
name: "Valid JSON array",
input: `["ticket:read", "ticket:write"]`,
want: []string{"ticket:read", "ticket:write"},
shouldError: false,
},
{
name: "Empty array",
input: "[]",
want: []string{},
shouldError: false,
},
{
name: "Invalid JSON",
input: "not json",
want: nil,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := FromJSONArray(t.Context(), tt.input)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("FromJSONArray() = %v, want %v", got, tt.want)
}
})
}
}
func TestToJSONArray(t *testing.T) {
t.Parallel()
tests := []struct {
name string
input []string
want string
}{
{
name: "Valid permissions array",
input: []string{"ticket:read", "ticket:write"},
want: `["ticket:read","ticket:write"]`,
},
{
name: "Empty array",
input: []string{},
want: "[]",
},
{
name: "Nil array",
input: nil,
want: "[]",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := ToJSONArray(t.Context(), tt.input)
if got != tt.want {
t.Errorf("ToJSONArray() = %v, want %v", got, tt.want)
}
})
}
}

178
app/auth/resetpassword.go Normal file
View File

@@ -0,0 +1,178 @@
package auth
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/SecurityBrewery/catalyst/app/auth/password"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/mail"
"github.com/SecurityBrewery/catalyst/app/settings"
)
func handleResetPasswordMail(queries *sqlc.Queries, mailer *mail.Mailer) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
type passwordResetData struct {
Email string `json:"email"`
}
b, err := json.Marshal(map[string]any{
"message": "Password reset email sent when the user exists",
})
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to create response: "+err.Error())
return
}
var data passwordResetData
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
errorJSON(w, http.StatusBadRequest, "Invalid request, missing email field")
return
}
user, err := queries.UserByEmail(r.Context(), &data.Email)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// Do not reveal whether the user exists or not
w.WriteHeader(http.StatusOK)
_, _ = w.Write(b)
return
}
errorJSON(w, http.StatusInternalServerError, "Failed to get user: "+err.Error())
return
}
settings, err := settings.Load(r.Context(), queries)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to load settings: "+err.Error())
return
}
resetToken, err := createResetToken(&user, settings)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to create reset token: "+err.Error())
return
}
link := settings.Meta.AppURL + "/ui/password-reset?mail=" + data.Email + "&token=" + resetToken
subject := settings.Meta.ResetPasswordTemplate.Subject
subject = strings.ReplaceAll(subject, "{APP_NAME}", settings.Meta.AppName)
plainTextBody := `Hello,
Thank you for joining us at {APP_NAME}.
Click on the link below to verify your email address or copy the token into the app:
{ACTION_URL}
Thanks, {APP_NAME} team`
plainTextBody = strings.ReplaceAll(plainTextBody, "{ACTION_URL}", link)
plainTextBody = strings.ReplaceAll(plainTextBody, "{APP_NAME}", settings.Meta.AppName)
htmlBody := settings.Meta.ResetPasswordTemplate.Body
htmlBody = strings.ReplaceAll(htmlBody, "{ACTION_URL}", link)
htmlBody = strings.ReplaceAll(htmlBody, "{APP_NAME}", settings.Meta.AppName)
if err := mailer.Send(r.Context(), data.Email, subject, plainTextBody, htmlBody); err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to send password reset email: "+err.Error())
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(b)
}
}
func handlePassword(queries *sqlc.Queries) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
type passwordResetData struct {
Token string `json:"token"`
Email string `json:"email"`
Password string `json:"password"`
PasswordConfirm string `json:"password_confirm"`
}
var data passwordResetData
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
errorJSON(w, http.StatusBadRequest, "Invalid request, missing email or password fields")
return
}
if data.Password != data.PasswordConfirm {
errorJSON(w, http.StatusBadRequest, "Passwords do not match")
return
}
user, err := queries.UserByEmail(r.Context(), &data.Email)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
errorJSON(w, http.StatusBadRequest, "Invalid or expired reset token")
return
}
errorJSON(w, http.StatusInternalServerError, "Failed to get user: "+err.Error())
return
}
settings, err := settings.Load(r.Context(), queries)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to load settings: "+err.Error())
return
}
if err := verifyResetToken(data.Token, &user, settings.Meta.AppURL, settings.RecordPasswordResetToken.Secret); err != nil {
errorJSON(w, http.StatusBadRequest, "Invalid or expired reset token: "+err.Error())
return
}
passwordHash, tokenKey, err := password.Hash(data.Password)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to hash password: "+err.Error())
return
}
now := time.Now().UTC()
if _, err := queries.UpdateUser(r.Context(), sqlc.UpdateUserParams{
ID: user.ID,
PasswordHash: &passwordHash,
TokenKey: &tokenKey,
LastResetSentAt: &now,
}); err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to update password: "+err.Error())
return
}
b, err := json.Marshal(map[string]any{
"message": "Password reset successfully",
})
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to create response: "+err.Error())
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write(b)
}
}

View File

@@ -0,0 +1,94 @@
package auth
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
func TestService_createResetToken(t *testing.T) {
t.Parallel()
type args struct {
createUser *sqlc.User
tokenDuration time.Duration
waitDuration time.Duration
verifyUser *sqlc.User
}
tests := []struct {
name string
args args
wantErr assert.ErrorAssertionFunc
}{
{
name: "valid token",
args: args{
createUser: &sqlc.User{ID: "testuser", Tokenkey: "testtoken"},
tokenDuration: time.Hour,
waitDuration: 0,
verifyUser: &sqlc.User{
ID: "testuser",
Tokenkey: "testtoken",
Updated: mustParse(t, "2006-01-02 15:04:05Z", "2025-06-02 19:18:06.292Z"),
},
},
wantErr: assert.NoError,
},
{
name: "expired token",
args: args{
createUser: &sqlc.User{ID: "testuser", Tokenkey: "testtoken"},
tokenDuration: 0,
waitDuration: time.Second,
verifyUser: &sqlc.User{
ID: "testuser",
Tokenkey: "testtoken",
Updated: mustParse(t, "2006-01-02 15:04:05Z", "2025-06-02 19:18:06.292Z"),
},
},
wantErr: assert.Error,
},
{
name: "invalid token",
args: args{
createUser: &sqlc.User{ID: "testuser", Tokenkey: "testtoken"},
tokenDuration: time.Hour,
waitDuration: 0,
verifyUser: &sqlc.User{
ID: "invaliduser",
Tokenkey: "invalidtoken",
Updated: mustParse(t, "2006-01-02 15:04:05Z", "2025-06-02 19:18:06.292Z"),
},
},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, err := createResetTokenWithDuration(tt.args.createUser, "", "", tt.args.tokenDuration)
require.NoError(t, err, "createResetToken()")
time.Sleep(tt.args.waitDuration)
err = verifyResetToken(got, tt.args.verifyUser, "", "")
tt.wantErr(t, err, "verifyResetToken()")
})
}
}
func mustParse(t *testing.T, layout, value string) time.Time {
t.Helper()
parsed, err := time.Parse(layout, value)
require.NoError(t, err, "mustParse()")
return parsed
}

58
app/auth/server.go Normal file
View File

@@ -0,0 +1,58 @@
package auth
import (
"encoding/json"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/mail"
)
func Server(queries *sqlc.Queries, mailer *mail.Mailer) http.Handler {
router := chi.NewRouter()
router.Get("/user", handleUser(queries))
router.Post("/local/login", handleLogin(queries))
router.Post("/local/reset-password-mail", handleResetPasswordMail(queries, mailer))
router.Post("/local/reset-password", handlePassword(queries))
return router
}
func handleUser(queries *sqlc.Queries) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
authorizationHeader := r.Header.Get("Authorization")
bearerToken := strings.TrimPrefix(authorizationHeader, bearerPrefix)
user, _, err := verifyAccessToken(r.Context(), bearerToken, queries)
if err != nil {
_, _ = w.Write([]byte("null"))
return
}
permissions, err := queries.ListUserPermissions(r.Context(), user.ID)
if err != nil {
errorJSON(w, http.StatusInternalServerError, err.Error())
return
}
b, err := json.Marshal(map[string]any{
"user": user,
"permissions": permissions,
})
if err != nil {
errorJSON(w, http.StatusInternalServerError, err.Error())
return
}
r.Header.Set("Content-Type", "application/json")
_, _ = w.Write(b)
}
}

99
app/auth/server_local.go Normal file
View File

@@ -0,0 +1,99 @@
package auth
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"golang.org/x/crypto/bcrypt"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/settings"
)
var ErrUserInactive = errors.New("user is inactive")
func handleLogin(queries *sqlc.Queries) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
type loginData struct {
Email string `json:"email"`
Password string `json:"password"`
}
var data loginData
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
unauthorizedJSON(w, "Invalid request")
return
}
user, err := loginWithMail(r.Context(), data.Email, data.Password, queries)
if err != nil {
if errors.Is(err, ErrUserInactive) {
unauthorizedJSON(w, "User is inactive")
return
}
unauthorizedJSON(w, "Login failed")
return
}
permissions, err := queries.ListUserPermissions(r.Context(), user.ID)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to get user permissions")
return
}
settings, err := settings.Load(r.Context(), queries)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to load settings")
return
}
duration := time.Duration(settings.RecordAuthToken.Duration) * time.Second
token, err := CreateAccessToken(r.Context(), user, permissions, duration, queries)
if err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to create login token")
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
response := map[string]string{
"token": token,
}
if err := json.NewEncoder(w).Encode(response); err != nil {
errorJSON(w, http.StatusInternalServerError, "Failed to encode response")
return
}
}
}
func loginWithMail(ctx context.Context, mail, password string, queries *sqlc.Queries) (*sqlc.User, error) {
user, err := queries.UserByEmail(ctx, &mail)
if err != nil {
return nil, fmt.Errorf("failed to find user by email %q: %w", mail, err)
}
if !user.Active {
return nil, ErrUserInactive
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Passwordhash), []byte(password)); err != nil {
return nil, fmt.Errorf("invalid credentials: %w", err)
}
return &user, nil
}

225
app/auth/token.go Normal file
View File

@@ -0,0 +1,225 @@
package auth
import (
"context"
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/settings"
)
const (
purposeAccess = "access"
purposeReset = "reset"
scopeReset = "reset"
)
func CreateAccessToken(ctx context.Context, user *sqlc.User, permissions []string, duration time.Duration, queries *sqlc.Queries) (string, error) {
settings, err := settings.Load(ctx, queries)
if err != nil {
return "", fmt.Errorf("failed to load settings: %w", err)
}
return createToken(user, duration, purposeAccess, permissions, settings.Meta.AppURL, settings.RecordAuthToken.Secret)
}
func createResetToken(user *sqlc.User, settings *settings.Settings) (string, error) {
duration := time.Duration(settings.RecordPasswordResetToken.Duration) * time.Second
return createResetTokenWithDuration(user, settings.Meta.AppURL, settings.RecordPasswordResetToken.Secret, duration)
}
func createResetTokenWithDuration(user *sqlc.User, url, appToken string, duration time.Duration) (string, error) {
return createToken(user, duration, purposeReset, []string{scopeReset}, url, appToken)
}
func createToken(user *sqlc.User, duration time.Duration, purpose string, scopes []string, url, appToken string) (string, error) {
if scopes == nil {
scopes = []string{}
}
claims := jwt.MapClaims{
"sub": user.ID,
"exp": time.Now().Add(duration).Unix(),
"iat": time.Now().Unix(),
"iss": url,
"purpose": purpose,
"scopes": scopes,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signingKey := user.Tokenkey + appToken
return token.SignedString([]byte(signingKey))
}
func verifyToken(tokenStr string, user *sqlc.User, url, appToken string) (jwt.MapClaims, error) { //nolint:cyclop
signingKey := user.Tokenkey + appToken
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected algorithm: %v", t.Header["alg"])
}
return []byte(signingKey), nil
})
if err != nil {
return nil, fmt.Errorf("failed to verify token: %w", err)
}
if !token.Valid {
return nil, fmt.Errorf("token invalid")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, fmt.Errorf("invalid token claims")
}
iss, err := claims.GetIssuer()
if err != nil {
return nil, fmt.Errorf("failed to get issuer: %w", err)
}
if iss != url {
return nil, fmt.Errorf("token issued by a different server")
}
sub, err := claims.GetSubject()
if err != nil {
return nil, fmt.Errorf("failed to get subject: %w", err)
}
if sub != user.ID {
return nil, fmt.Errorf("token belongs to a different user")
}
iat, err := claims.GetExpirationTime()
if err != nil {
return nil, fmt.Errorf("failed to get expiration time: %w", err)
}
if iat.Before(time.Now()) {
return nil, fmt.Errorf("token expired")
}
return claims, nil
}
func verifyAccessToken(ctx context.Context, bearerToken string, queries *sqlc.Queries) (*sqlc.User, jwt.MapClaims, error) {
token, _, err := jwt.NewParser().ParseUnverified(bearerToken, jwt.MapClaims{})
if err != nil {
return nil, nil, fmt.Errorf("failed to parse token: %w", err)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, nil, fmt.Errorf("failed to parse token claims")
}
sub, err := claims.GetSubject()
if err != nil {
return nil, nil, fmt.Errorf("token invalid: %w", err)
}
user, err := queries.GetUser(ctx, sub)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve user for subject %s: %w", sub, err)
}
settings, err := settings.Load(ctx, queries)
if err != nil {
return nil, nil, fmt.Errorf("failed to load settings: %w", err)
}
claims, err = verifyToken(bearerToken, &user, settings.Meta.AppURL, settings.RecordAuthToken.Secret)
if err != nil {
return nil, nil, fmt.Errorf("failed to verify token: %w", err)
}
if err := hasPurpose(claims, purposeAccess); err != nil {
return nil, nil, fmt.Errorf("failed to check scopes: %w", err)
}
return &user, claims, nil
}
func verifyResetToken(tokenStr string, user *sqlc.User, url, appToken string) error {
claims, err := verifyToken(tokenStr, user, url, appToken)
if err != nil {
return err
}
iat, err := claims.GetIssuedAt()
if err != nil {
return fmt.Errorf("failed to get issued at: %w", err)
}
lastUpdated := user.Updated // TODO: create a last reset at column
if iat.Before(lastUpdated) {
return fmt.Errorf("token already used")
}
if err := hasPurpose(claims, purposeReset); err != nil {
return fmt.Errorf("failed to check scopes: %w", err)
}
return nil
}
func hasPurpose(claim jwt.MapClaims, expectedPurpose string) error {
purpose, err := purpose(claim)
if err != nil {
return fmt.Errorf("failed to get purposes: %w", err)
}
if purpose != expectedPurpose {
return fmt.Errorf("token has wrong purpose: %s, expected: %s", purpose, expectedPurpose)
}
return nil
}
func purpose(claim jwt.MapClaims) (string, error) {
purposeClaim, ok := claim["purpose"]
if !ok {
return "", fmt.Errorf("no purpose found")
}
purpose, ok := purposeClaim.(string)
if !ok {
return "", fmt.Errorf("invalid purpose type")
}
return purpose, nil
}
func scopes(claim jwt.MapClaims) ([]string, error) {
scopesClaim, ok := claim["scopes"]
if !ok {
return nil, fmt.Errorf("no scopes found")
}
scopesSlice, ok := scopesClaim.([]any)
if !ok {
return nil, fmt.Errorf("invalid scopes claim type: %T", scopesClaim)
}
scopes := make([]string, 0, len(scopesSlice))
for _, scope := range scopesSlice {
scopeStr, ok := scope.(string)
if !ok {
return nil, fmt.Errorf("invalid scope claim element type: %T", scope)
}
scopes = append(scopes, scopeStr)
}
return scopes, nil
}

View File

@@ -0,0 +1,46 @@
package usercontext
import (
"context"
"net/http"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
type userKey struct{}
func UserRequest(r *http.Request, user *sqlc.User) *http.Request {
return r.WithContext(UserContext(r.Context(), user))
}
func UserContext(ctx context.Context, user *sqlc.User) context.Context {
return context.WithValue(ctx, userKey{}, user)
}
func UserFromContext(ctx context.Context) (*sqlc.User, bool) {
user, ok := ctx.Value(userKey{}).(*sqlc.User)
if !ok {
return nil, false
}
return user, true
}
type permissionKey struct{}
func PermissionRequest(r *http.Request, permissions []string) *http.Request {
return r.WithContext(PermissionContext(r.Context(), permissions))
}
func PermissionContext(ctx context.Context, permissions []string) context.Context {
return context.WithValue(ctx, permissionKey{}, permissions)
}
func PermissionFromContext(ctx context.Context) ([]string, bool) {
permissions, ok := ctx.Value(permissionKey{}).([]string)
if !ok {
return nil, false
}
return permissions, true
}

View File

@@ -0,0 +1,116 @@
package usercontext
import (
"net/http"
"reflect"
"testing"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
func TestPermissionContext(t *testing.T) {
t.Parallel()
tests := []struct {
name string
user *sqlc.User
permissions []string
wantPerms []string
wantOk bool
}{
{
name: "Set and get permissions",
permissions: []string{"ticket:read", "ticket:write"},
wantPerms: []string{"ticket:read", "ticket:write"},
wantOk: true,
},
{
name: "No permissions set",
wantPerms: nil,
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Test context functions
ctx := PermissionContext(t.Context(), tt.permissions)
gotPerms, gotOk := PermissionFromContext(ctx)
if !reflect.DeepEqual(gotPerms, tt.wantPerms) {
t.Errorf("PermissionFromContext() got perms = %v, want %v", gotPerms, tt.wantPerms)
}
if gotOk != tt.wantOk {
t.Errorf("PermissionFromContext() got ok = %v, want %v", gotOk, tt.wantOk)
}
// Test request functions
req := &http.Request{}
req = PermissionRequest(req, tt.permissions)
gotPerms, gotOk = PermissionFromContext(req.Context())
if !reflect.DeepEqual(gotPerms, tt.wantPerms) {
t.Errorf("PermissionFromContext() got perms = %v, want %v", gotPerms, tt.wantPerms)
}
if gotOk != tt.wantOk {
t.Errorf("PermissionFromContext() got ok = %v, want %v", gotOk, tt.wantOk)
}
})
}
}
func TestUserContext(t *testing.T) {
t.Parallel()
tests := []struct {
name string
user *sqlc.User
wantOk bool
}{
{
name: "Set and get user",
user: &sqlc.User{ID: "test-user"},
wantOk: true,
},
{
name: "No user set",
user: nil,
wantOk: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// Test context functions
ctx := UserContext(t.Context(), tt.user)
gotUser, gotOk := UserFromContext(ctx)
if !reflect.DeepEqual(gotUser, tt.user) {
t.Errorf("UserFromContext() got user = %v, want %v", gotUser, tt.user)
}
if gotOk != tt.wantOk {
t.Errorf("UserFromContext() got ok = %v, want %v", gotOk, tt.wantOk)
}
// Test request functions
req := &http.Request{}
req = UserRequest(req, tt.user)
gotUser, gotOk = UserFromContext(req.Context())
if !reflect.DeepEqual(gotUser, tt.user) {
t.Errorf("UserFromContext() got user = %v, want %v", gotUser, tt.user)
}
if gotOk != tt.wantOk {
t.Errorf("UserFromContext() got ok = %v, want %v", gotOk, tt.wantOk)
}
})
}
}

36
app/counter/counter.go Normal file
View File

@@ -0,0 +1,36 @@
package counter
import "sync"
type Counter struct {
mux sync.Mutex
counts map[string]int
}
func NewCounter() *Counter {
return &Counter{
counts: make(map[string]int),
}
}
func (c *Counter) Increment(name string) {
c.mux.Lock()
defer c.mux.Unlock()
if _, ok := c.counts[name]; !ok {
c.counts[name] = 0
}
c.counts[name]++
}
func (c *Counter) Count(name string) int {
c.mux.Lock()
defer c.mux.Unlock()
if _, ok := c.counts[name]; !ok {
return 0
}
return c.counts[name]
}

View File

@@ -0,0 +1,41 @@
package counter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCounter(t *testing.T) {
t.Parallel()
type args struct {
name string
repeat int
}
tests := []struct {
name string
args args
want int
}{
{
name: "Test Counter",
args: args{name: "test", repeat: 5},
want: 5,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
c := NewCounter()
for range tt.args.repeat {
c.Increment(tt.args.name)
}
assert.Equal(t, tt.want, c.Count(tt.args.name))
})
}
}

442
app/data/demo.go Normal file
View File

@@ -0,0 +1,442 @@
package data
import (
"context"
_ "embed"
"fmt"
"time"
"github.com/brianvoe/gofakeit/v7"
"github.com/SecurityBrewery/catalyst/app/auth"
"github.com/SecurityBrewery/catalyst/app/auth/password"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
const (
minimumUserCount = 1
minimumTicketCount = 1
)
var (
//go:embed scripts/createticket.py
createTicketPy string
//go:embed scripts/alertingest.py
alertIngestPy string
//go:embed scripts/assigntickets.py
assignTicketsPy string
)
func GenerateDemoData(ctx context.Context, queries *sqlc.Queries, userCount, ticketCount int) error {
if userCount < minimumUserCount {
userCount = minimumUserCount
}
if ticketCount < minimumTicketCount {
ticketCount = minimumTicketCount
}
types, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListTypesRow, error) {
return queries.ListTypes(ctx, sqlc.ListTypesParams{Limit: limit, Offset: offset})
})
if err != nil {
return fmt.Errorf("failed to list types: %w", err)
}
users, err := generateDemoUsers(ctx, queries, userCount, ticketCount)
if err != nil {
return fmt.Errorf("failed to create user records: %w", err)
}
if len(types) == 0 {
return fmt.Errorf("no types found")
}
if len(users) == 0 {
return fmt.Errorf("no users found")
}
if err := generateDemoTickets(ctx, queries, users, types, ticketCount); err != nil {
return fmt.Errorf("failed to create ticket records: %w", err)
}
if err := generateDemoReactions(ctx, queries, ticketCount); err != nil {
return fmt.Errorf("failed to create reaction records: %w", err)
}
if err := generateDemoGroups(ctx, queries, users, ticketCount); err != nil {
return fmt.Errorf("failed to create group records: %w", err)
}
return nil
}
func generateDemoUsers(ctx context.Context, queries *sqlc.Queries, count, ticketCount int) ([]sqlc.User, error) {
users := make([]sqlc.User, 0, count)
// create the test user
user, err := queries.GetUser(ctx, "u_test")
if err != nil {
newUser, err := createTestUser(ctx, queries)
if err != nil {
return nil, err
}
users = append(users, newUser)
} else {
users = append(users, user)
}
for range count - 1 {
newUser, err := createDemoUser(ctx, queries, ticketCount)
if err != nil {
return nil, err
}
users = append(users, newUser)
}
return users, nil
}
func createDemoUser(ctx context.Context, queries *sqlc.Queries, ticketCount int) (sqlc.User, error) {
username := gofakeit.Username()
passwordHash, tokenKey, err := password.Hash(gofakeit.Password(true, true, true, true, false, 16))
if err != nil {
return sqlc.User{}, fmt.Errorf("failed to hash password: %w", err)
}
created, updated := dates(ticketCount)
return queries.InsertUser(ctx, sqlc.InsertUserParams{
ID: database.GenerateID("u"),
Name: pointer.Pointer(gofakeit.Name()),
Email: pointer.Pointer(username + "@catalyst-soar.com"),
Username: username,
PasswordHash: passwordHash,
TokenKey: tokenKey,
Active: gofakeit.Bool(),
Created: created,
Updated: updated,
})
}
var ticketCreated = time.Date(2025, 2, 1, 11, 29, 35, 0, time.UTC)
func generateDemoTickets(ctx context.Context, queries *sqlc.Queries, users []sqlc.User, types []sqlc.ListTypesRow, count int) error { //nolint:cyclop
for range count {
newTicket, err := createDemoTicket(ctx, queries, random(types), random(users).ID, fakeTicketTitle(), fakeTicketDescription(), count)
if err != nil {
return fmt.Errorf("failed to create ticket: %w", err)
}
for range gofakeit.IntRange(1, 5) {
_, err := createDemoComment(ctx, queries, newTicket.ID, random(users).ID, fakeTicketComment(), count)
if err != nil {
return fmt.Errorf("failed to create comment for ticket %s: %w", newTicket.ID, err)
}
}
for range gofakeit.IntRange(1, 5) {
_, err := createDemoTimeline(ctx, queries, newTicket.ID, fakeTicketTimelineMessage(), count)
if err != nil {
return fmt.Errorf("failed to create timeline for ticket %s: %w", newTicket.ID, err)
}
}
for range gofakeit.IntRange(1, 5) {
_, err := createDemoTask(ctx, queries, newTicket.ID, random(users).ID, fakeTicketTask(), count)
if err != nil {
return fmt.Errorf("failed to create task for ticket %s: %w", newTicket.ID, err)
}
}
for range gofakeit.IntRange(1, 5) {
_, err := createDemoLink(ctx, queries, newTicket.ID, random([]string{"Blog", "Forum", "Wiki", "Documentation"}), gofakeit.URL(), count)
if err != nil {
return fmt.Errorf("failed to create link for ticket %s: %w", newTicket.ID, err)
}
}
}
return nil
}
func createDemoTicket(ctx context.Context, queries *sqlc.Queries, ticketType sqlc.ListTypesRow, userID, name, description string, ticketCount int) (sqlc.Ticket, error) {
created, updated := dates(ticketCount)
ticket, err := queries.InsertTicket(
ctx,
sqlc.InsertTicketParams{
ID: database.GenerateID(ticketType.Singular),
Name: name,
Description: description,
Open: gofakeit.Bool(),
Owner: &userID,
Schema: marshal(map[string]any{"type": "object", "properties": map[string]any{"tlp": map[string]any{"title": "TLP", "type": "string"}}}),
State: marshal(map[string]any{"severity": "Medium"}),
Type: ticketType.ID,
Created: created,
Updated: updated,
},
)
if err != nil {
return sqlc.Ticket{}, fmt.Errorf("failed to create ticket for user %s: %w", userID, err)
}
return ticket, nil
}
func createDemoComment(ctx context.Context, queries *sqlc.Queries, ticketID, userID, message string, ticketCount int) (*sqlc.Comment, error) {
created, updated := dates(ticketCount)
comment, err := queries.InsertComment(ctx, sqlc.InsertCommentParams{
ID: database.GenerateID("c"),
Ticket: ticketID,
Author: userID,
Message: message,
Created: created,
Updated: updated,
})
if err != nil {
return nil, fmt.Errorf("failed to create comment for ticket %s: %w", ticketID, err)
}
return &comment, nil
}
func createDemoTimeline(ctx context.Context, queries *sqlc.Queries, ticketID, message string, ticketCount int) (*sqlc.Timeline, error) {
created, updated := dates(ticketCount)
timeline, err := queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
ID: database.GenerateID("tl"),
Ticket: ticketID,
Message: message,
Time: ticketCreated,
Created: created,
Updated: updated,
})
if err != nil {
return nil, fmt.Errorf("failed to create timeline for ticket %s: %w", ticketID, err)
}
return &timeline, nil
}
func createDemoTask(ctx context.Context, queries *sqlc.Queries, ticketID, userID, name string, ticketCount int) (*sqlc.Task, error) {
created, updated := dates(ticketCount)
task, err := queries.InsertTask(ctx, sqlc.InsertTaskParams{
ID: database.GenerateID("t"),
Ticket: ticketID,
Owner: &userID,
Name: name,
Open: gofakeit.Bool(),
Created: created,
Updated: updated,
})
if err != nil {
return nil, fmt.Errorf("failed to create task for ticket %s: %w", ticketID, err)
}
return &task, nil
}
func createDemoLink(ctx context.Context, queries *sqlc.Queries, ticketID, name, url string, ticketCount int) (*sqlc.Link, error) {
created, updated := dates(ticketCount)
link, err := queries.InsertLink(ctx, sqlc.InsertLinkParams{
ID: database.GenerateID("l"),
Ticket: ticketID,
Name: name,
Url: url,
Created: created,
Updated: updated,
})
if err != nil {
return nil, fmt.Errorf("failed to create link for ticket %s: %w", ticketID, err)
}
return &link, nil
}
func generateDemoReactions(ctx context.Context, queries *sqlc.Queries, ticketCount int) error {
created, updated := dates(ticketCount)
_, err := queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-schedule",
Name: "Create New Ticket",
Trigger: "schedule",
Triggerdata: marshal(map[string]any{"expression": "12 * * * *"}),
Action: "python",
Actiondata: marshal(map[string]any{
"requirements": "requests",
"script": createTicketPy,
}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create reaction for schedule trigger: %w", err)
}
created, updated = dates(ticketCount)
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-webhook",
Name: "Alert Ingest Webhook",
Trigger: "webhook",
Triggerdata: marshal(map[string]any{"token": "1234567890", "path": "webhook"}),
Action: "python",
Actiondata: marshal(map[string]any{
"requirements": "requests",
"script": alertIngestPy,
}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create reaction for webhook trigger: %w", err)
}
created, updated = dates(ticketCount)
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-hook",
Name: "Assign new Tickets",
Trigger: "hook",
Triggerdata: marshal(map[string]any{"collections": []any{"tickets"}, "events": []any{"create"}}),
Action: "python",
Actiondata: marshal(map[string]any{
"requirements": "requests",
"script": assignTicketsPy,
}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create reaction for hook trigger: %w", err)
}
return nil
}
func generateDemoGroups(ctx context.Context, queries *sqlc.Queries, users []sqlc.User, ticketCount int) error { //nolint:cyclop
created, updated := dates(ticketCount)
_, err := queries.InsertGroup(ctx, sqlc.InsertGroupParams{
ID: "team-ir",
Name: "IR Team",
Permissions: auth.ToJSONArray(ctx, []string{}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create IR team group: %w", err)
}
created, updated = dates(ticketCount)
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
ID: "team-seceng",
Name: "Security Engineering Team",
Permissions: auth.ToJSONArray(ctx, []string{}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create IR team group: %w", err)
}
created, updated = dates(ticketCount)
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
ID: "team-security",
Name: "Security Team",
Permissions: auth.ToJSONArray(ctx, []string{}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create security team group: %w", err)
}
created, updated = dates(ticketCount)
_, err = queries.InsertGroup(ctx, sqlc.InsertGroupParams{
ID: "g-engineer",
Name: "Engineer",
Permissions: auth.ToJSONArray(ctx, []string{"reaction:read", "reaction:write"}),
Created: created,
Updated: updated,
})
if err != nil {
return fmt.Errorf("failed to create analyst group: %w", err)
}
for _, user := range users {
group := gofakeit.RandomString([]string{"team-seceng", "team-ir"})
if user.ID == "u_test" {
group = "admin"
}
if err := queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
UserID: user.ID,
GroupID: group,
}); err != nil {
return fmt.Errorf("failed to assign group %s to user %s: %w", group, user.ID, err)
}
}
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
ParentGroupID: "team-ir",
ChildGroupID: "analyst",
})
if err != nil {
return fmt.Errorf("failed to assign parent group: %w", err)
}
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
ParentGroupID: "team-seceng",
ChildGroupID: "g-engineer",
})
if err != nil {
return fmt.Errorf("failed to assign parent group: %w", err)
}
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
ParentGroupID: "team-ir",
ChildGroupID: "team-security",
})
if err != nil {
return fmt.Errorf("failed to assign parent group: %w", err)
}
err = queries.AssignParentGroup(ctx, sqlc.AssignParentGroupParams{
ParentGroupID: "team-seceng",
ChildGroupID: "team-security",
})
if err != nil {
return fmt.Errorf("failed to assign parent group: %w", err)
}
return nil
}
func weeksAgo(c int) time.Time {
return time.Now().UTC().AddDate(0, 0, -7*c)
}
func dates(ticketCount int) (time.Time, time.Time) {
const ticketsPerWeek = 10
weeks := ticketCount / ticketsPerWeek
created := gofakeit.DateRange(weeksAgo(1), weeksAgo(weeks+1)).UTC()
updated := gofakeit.DateRange(created, time.Now()).UTC()
return created, updated
}

26
app/data/demo_test.go Normal file
View File

@@ -0,0 +1,26 @@
package data_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/data"
catalystTesting "github.com/SecurityBrewery/catalyst/testing"
)
func TestGenerate(t *testing.T) {
t.Parallel()
app, cleanup, _ := catalystTesting.App(t)
t.Cleanup(cleanup)
_ = app.Queries.DeleteUser(t.Context(), "u_admin")
_ = app.Queries.DeleteUser(t.Context(), "u_bob_analyst")
_ = app.Queries.DeleteGroup(t.Context(), "g_admin")
_ = app.Queries.DeleteGroup(t.Context(), "g_analyst")
err := data.GenerateDemoData(t.Context(), app.Queries, 4, 4)
require.NoError(t, err, "failed to generate fake data")
}

View File

@@ -0,0 +1,20 @@
import sys
import json
import random
import os
import requests
# Parse the event from the webhook payload
event = json.loads(sys.argv[1])
body = json.loads(event["body"])
url = os.environ["CATALYST_APP_URL"]
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
# Create a new ticket
requests.post(url + "/api/tickets", headers=header, json={
"name": body["name"],
"type": "alert",
"open": True,
})

View File

@@ -0,0 +1,21 @@
import sys
import json
import random
import os
import requests
# Parse the ticket from the input
ticket = json.loads(sys.argv[1])
url = os.environ["CATALYST_APP_URL"]
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
# Get a random user
users = requests.get(url + "/api/users", headers=header).json()
random_user = random.choice(users)
# Assign the ticket to the random user
requests.patch(url + "/api/tickets/" + ticket["record"]["id"], headers=header, json={
"owner": random_user["id"]
})

View File

@@ -0,0 +1,20 @@
import sys
import json
import random
import os
import requests
url = os.environ["CATALYST_APP_URL"]
header = {"Authorization": "Bearer " + os.environ["CATALYST_TOKEN"]}
newtickets = requests.get(url + "/api/tickets?limit=3", headers=header).json()
for ticket in newtickets:
requests.delete(url + "/api/tickets/" + ticket["id"], headers=header)
# Create a new ticket
requests.post(url + "/api/tickets", headers=header, json={
"name": "New Ticket",
"type": "alert",
"open": True,
})

View File

@@ -0,0 +1,21 @@
import sys
import json
import random
import os
from pocketbase import PocketBase
# Connect to the PocketBase server
client = PocketBase(os.environ["CATALYST_APP_URL"])
client.auth_store.save(token=os.environ["CATALYST_TOKEN"])
newtickets = client.collection("tickets").get_list(1, 200, {"filter": 'name = "New Ticket"'})
for ticket in newtickets.items:
client.collection("tickets").delete(ticket.id)
# Create a new ticket
client.collection("tickets").create({
"name": "New Ticket",
"type": "alert",
"open": True,
})

227
app/data/testdata.go Normal file
View File

@@ -0,0 +1,227 @@
package data
import (
"context"
"encoding/json"
"log/slog"
"os"
"path"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
const (
AdminEmail = "admin@catalyst-soar.com"
AnalystEmail = "analyst@catalyst-soar.com"
)
func DefaultTestData(t *testing.T, dir string, queries *sqlc.Queries) {
t.Helper()
parseTime := func(s string) time.Time {
t, _ := time.Parse(time.RFC3339Nano, s)
return t
}
ctx := t.Context()
// Insert users
_, err := queries.InsertUser(ctx, sqlc.InsertUserParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
Email: pointer.Pointer("analyst@catalyst-soar.com"),
Username: "u_bob_analyst",
Name: pointer.Pointer("Bob Analyst"),
PasswordHash: "$2a$10$ZEHNh9ZKJ81N717wovDnMuLwZOLa6.g22IRzRr4goG6zGN.57UzJG",
TokenKey: "z3Jj8bbzcq_cSZs07XKoGlB0UtvmQiphHgwNkE4akoY=",
Active: true,
ID: "u_bob_analyst",
})
require.NoError(t, err, "failed to insert analyst user")
_, err = queries.InsertUser(ctx, sqlc.InsertUserParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
Email: pointer.Pointer("admin@catalyst-soar.com"),
Username: "u_admin",
Name: pointer.Pointer("Admin User"),
PasswordHash: "$2a$10$Z3/0HHWau6oi1t1aRPiI0uiVOWI.IosTAYEL0DJ2XJaalP9kesgBa",
TokenKey: "5BWDKLIAn3SQkpQlBUGrS_XEbFf91DsDpuh_Xmt4Nwg=",
Active: true,
ID: "u_admin",
})
require.NoError(t, err, "failed to insert admin user")
// Insert webhooks
_, err = queries.InsertWebhook(ctx, sqlc.InsertWebhookParams{
ID: "w_test_webhook",
Name: "Test Webhook",
Collection: "tickets",
Destination: "https://example.com",
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert webhook")
// Insert types
_, err = queries.InsertType(ctx, sqlc.InsertTypeParams{
ID: "test-type",
Singular: "Test",
Plural: "Tests",
Schema: []byte(`{}`),
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert type")
// Insert tickets
_, err = queries.InsertTicket(ctx, sqlc.InsertTicketParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
Description: "This is a test ticket.",
ID: "test-ticket",
Name: "Test Ticket",
Open: true,
Owner: pointer.Pointer("u_bob_analyst"),
Schema: json.RawMessage(`{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}`),
State: json.RawMessage(`{"tlp":"AMBER"}`),
Type: "incident",
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert ticket")
// Insert tasks
_, err = queries.InsertTask(ctx, sqlc.InsertTaskParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
ID: "k_test_task",
Name: "Test Task",
Open: true,
Owner: pointer.Pointer("u_bob_analyst"),
Ticket: "test-ticket",
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert task")
// Insert comments
_, err = queries.InsertComment(ctx, sqlc.InsertCommentParams{
Author: "u_bob_analyst",
Created: parseTime("2025-06-21T22:21:26.271Z"),
ID: "c_test_comment",
Message: "Initial comment on the test ticket.",
Ticket: "test-ticket",
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert comment")
// Insert timeline
_, err = queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
ID: "h_test_timeline",
Message: "Initial timeline entry.",
Ticket: "test-ticket",
Time: parseTime("2023-01-01T00:00:00Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert timeline entry")
// Insert links
_, err = queries.InsertLink(ctx, sqlc.InsertLinkParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
ID: "l_test_link",
Name: "Catalyst",
Ticket: "test-ticket",
Updated: parseTime("2025-06-21T22:21:26.271Z"),
Url: "https://example.com",
})
require.NoError(t, err, "failed to insert link")
// Insert files
_, err = queries.InsertFile(ctx, sqlc.InsertFileParams{
Created: parseTime("2025-06-21T22:21:26.271Z"),
ID: "b_test_file",
Name: "hello.txt",
Size: 5,
Ticket: "test-ticket",
Updated: parseTime("2025-06-21T22:21:26.271Z"),
Blob: "hello_a20DUE9c77rj.txt",
})
require.NoError(t, err, "failed to insert file")
// Insert features
_, err = queries.CreateFeature(ctx, "dev")
require.NoError(t, err, "failed to insert feature 'dev'")
// Insert reactions
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-test-webhook",
Name: "Reaction",
Action: "python",
Actiondata: []byte(`{"requirements":"requests","script":"print('Hello, World!')"}`),
Trigger: "webhook",
Triggerdata: []byte(`{"token":"1234567890","path":"test"}`),
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert reaction")
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-test-proxy",
Action: "webhook",
Name: "Reaction",
Actiondata: []byte(`{"headers":{"Content-Type":"application/json"},"url":"http://127.0.0.1:12345/webhook"}`),
Trigger: "webhook",
Triggerdata: []byte(`{"path":"test2"}`),
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert reaction")
_, err = queries.InsertReaction(ctx, sqlc.InsertReactionParams{
ID: "r-test-hook",
Name: "Hook",
Action: "python",
Actiondata: []byte(`{"requirements":"requests","script":"import requests\nrequests.post('http://127.0.0.1:12346/test', json={'test':True})"}`),
Trigger: "hook",
Triggerdata: json.RawMessage(`{"collections":["tickets"],"events":["create"]}`),
Created: parseTime("2025-06-21T22:21:26.271Z"),
Updated: parseTime("2025-06-21T22:21:26.271Z"),
})
require.NoError(t, err, "failed to insert reaction")
// Insert user_groups
err = queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
UserID: "u_bob_analyst",
GroupID: "analyst",
})
require.NoError(t, err, "failed to assign analyst group to user")
err = queries.AssignGroupToUser(ctx, sqlc.AssignGroupToUserParams{
UserID: "u_admin",
GroupID: "admin",
})
require.NoError(t, err, "failed to assign admin group to user")
files, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListFilesRow, error) {
return queries.ListFiles(ctx, sqlc.ListFilesParams{Limit: limit, Offset: offset})
})
require.NoError(t, err, "failed to list files")
for _, file := range files {
_ = os.MkdirAll(path.Join(dir, "uploads", file.ID), 0o755)
infoFilePath := path.Join(dir, "uploads", file.ID+".info")
slog.InfoContext(t.Context(), "Creating file info", "path", infoFilePath)
err = os.WriteFile(infoFilePath, []byte(`{"MetaData":{"filetype":"text/plain"}}`), 0o600)
require.NoError(t, err, "failed to write file info")
err = os.WriteFile(path.Join(dir, "uploads", file.ID, file.Blob), []byte("hello"), 0o600)
require.NoError(t, err, "failed to write file blob")
}
}

16
app/data/testdata.sql Normal file
View File

@@ -0,0 +1,16 @@
INSERT INTO users VALUES('2025-06-21 22:21:26.271Z','2025-06-21 22:21:26.271Z','analyst@catalyst-soar.com','u_bob_analyst','','','Bob Analyst','$2a$10$ZEHNh9ZKJ81N717wovDnMuLwZOLa6.g22IRzRr4goG6zGN.57UzJG','z3Jj8bbzcq_cSZs07XKoGlB0UtvmQiphHgwNkE4akoY=','2025-06-21 22:21:26.271Z','u_bob_analyst',1);
INSERT INTO users VALUES('2025-06-21 22:21:26.271Z','2025-06-21 22:21:26.271Z','admin@catalyst-soar.com','u_admin','','','Admin User','$2a$10$Z3/0HHWau6oi1t1aRPiI0uiVOWI.IosTAYEL0DJ2XJaalP9kesgBa','5BWDKLIAn3SQkpQlBUGrS_XEbFf91DsDpuh_Xmt4Nwg=','2025-06-21 22:21:26.271Z','u_admin',1);
INSERT INTO webhooks VALUES('tickets','2025-06-21 22:21:26.271Z','https://example.com','w_test_webhook','Test Webhook','2025-06-21 22:21:26.271Z');
INSERT INTO types VALUES('2025-06-21 22:21:26.271Z','Bug','test-type','Tests','{}','Test','2025-06-21 22:21:26.271Z');
INSERT INTO tickets VALUES('2025-06-21 22:21:26.271Z','This is a test ticket.','test-ticket','Test Ticket',1,'u_bob_analyst','','{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}','{"tlp":"AMBER"}','incident','2025-06-21 22:21:26.271Z');
INSERT INTO tasks VALUES('2025-06-21 22:21:26.271Z','k_test_task','Test Task',1,'u_bob_analyst','test-ticket','2025-06-21 22:21:26.271Z');
INSERT INTO comments VALUES('u_bob_analyst','2025-06-21 22:21:26.271Z','c_test_comment','Initial comment on the test ticket.','test-ticket','2025-06-21 22:21:26.271Z');
INSERT INTO timeline VALUES('2025-06-21 22:21:26.271Z','h_test_timeline','Initial timeline entry.','test-ticket','2023-01-01T00:00:00Z','2025-06-21 22:21:26.271Z');
INSERT INTO links VALUES('2025-06-21 22:21:26.271Z','l_test_link','Catalyst','test-ticket','2025-06-21 22:21:26.271Z','https://example.com');
INSERT INTO files VALUES('hello_a20DUE9c77rj.txt','2025-06-21 22:21:26.271Z','b_test_file','hello.txt',5,'test-ticket','2025-06-21 22:21:26.271Z');
INSERT INTO features VALUES('2025-06-21 22:21:26.271Z','rce91818107f46a','dev','2025-06-21 22:21:26.271Z');
INSERT INTO reactions VALUES('python','{"requirements":"requests","script":"print(''Hello, World!'')"}','','r-test-webhook','Reaction','webhook','{"token":"1234567890","path":"test"}','2025-06-21 22:21:26.271Z');
INSERT INTO reactions VALUES('webhook','{"headers":{"Content-Type":"application/json"},"url":"http://127.0.0.1:12345/webhook"}','','r-test-proxy','Reaction','webhook','{"path":"test2"}','2025-06-21 22:21:26.271Z');
INSERT INTO reactions VALUES('python','{"requirements":"requests","script":"import requests\nrequests.post(''http://127.0.0.1:12346/test'', json={''test'':True})"}','','r-test-hook','Hook','hook','{"collections":["tickets"],"events":["create"]}','2025-06-21 22:21:26.271Z');
INSERT INTO user_groups VALUES('u_bob_analyst','analyst');
INSERT INTO user_groups VALUES('u_admin','admin');

84
app/data/testdata_test.go Normal file
View File

@@ -0,0 +1,84 @@
package data
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
func TestDBInitialization(t *testing.T) {
t.Parallel()
queries := NewTestDB(t, t.TempDir())
user, err := queries.SystemUser(t.Context())
require.NoError(t, err)
assert.Equal(t, "system", user.ID)
types, err := queries.ListTypes(t.Context(), sqlc.ListTypesParams{Offset: 0, Limit: 10})
require.NoError(t, err)
assert.GreaterOrEqual(t, len(types), 1)
}
func TestNewTestDBDefaultData(t *testing.T) {
t.Parallel()
queries := NewTestDB(t, t.TempDir())
user, err := queries.UserByEmail(t.Context(), pointer.Pointer(AdminEmail))
require.NoError(t, err)
assert.Equal(t, AdminEmail, *user.Email)
ticket, err := queries.Ticket(t.Context(), "test-ticket")
require.NoError(t, err)
assert.Equal(t, "test-ticket", ticket.ID)
comment, err := queries.GetComment(t.Context(), "c_test_comment")
require.NoError(t, err)
assert.Equal(t, "c_test_comment", comment.ID)
timeline, err := queries.GetTimeline(t.Context(), "h_test_timeline")
require.NoError(t, err)
assert.Equal(t, "h_test_timeline", timeline.ID)
}
func TestReadWrite(t *testing.T) {
t.Parallel()
queries := NewTestDB(t, t.TempDir())
for range 3 {
y, err := queries.CreateType(t.Context(), sqlc.CreateTypeParams{
Singular: "Foo",
Plural: "Foos",
Icon: pointer.Pointer("Bug"),
Schema: json.RawMessage("{}"),
})
require.NoError(t, err)
_, err = queries.GetType(t.Context(), y.ID)
require.NoError(t, err)
err = queries.DeleteType(t.Context(), y.ID)
require.NoError(t, err)
}
}
func TestRead(t *testing.T) {
t.Parallel()
queries := NewTestDB(t, t.TempDir())
// read from a table
_, err := queries.GetUser(t.Context(), "u_bob_analyst")
require.NoError(t, err)
// read from a view
_, err = queries.GetSidebar(t.Context())
require.NoError(t, err)
}

27
app/data/testing.go Normal file
View File

@@ -0,0 +1,27 @@
package data
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/migration"
"github.com/SecurityBrewery/catalyst/app/upload"
)
func NewTestDB(t *testing.T, dir string) *sqlc.Queries {
t.Helper()
queries := database.TestDB(t, dir)
uploader, err := upload.New(dir)
require.NoError(t, err)
err = migration.Apply(t.Context(), queries, dir, uploader)
require.NoError(t, err)
DefaultTestData(t, dir, queries)
return queries
}

40
app/data/testing_test.go Normal file
View File

@@ -0,0 +1,40 @@
package data
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
func TestNewTestDB(t *testing.T) {
t.Parallel()
dir := t.TempDir()
queries := NewTestDB(t, dir)
user, err := queries.GetUser(t.Context(), "u_bob_analyst")
require.NoError(t, err)
assert.Equal(t, "u_bob_analyst", user.ID)
assert.Equal(t, "Bob Analyst", *user.Name)
assert.Equal(t, time.Date(2025, time.June, 21, 22, 21, 26, 271000000, time.UTC), user.Created)
alice, err := queries.InsertUser(t.Context(), sqlc.InsertUserParams{
ID: "u_alice_admin",
Name: pointer.Pointer("Alice Admin"),
Username: "alice_admin",
PasswordHash: "",
TokenKey: "",
Created: time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC),
Updated: time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC),
})
require.NoError(t, err)
assert.Equal(t, time.Date(2025, time.June, 21, 22, 21, 26, 0, time.UTC), alice.Created)
}

127
app/data/text.go Normal file
View File

@@ -0,0 +1,127 @@
package data
import "github.com/brianvoe/gofakeit/v7"
func fakeTicketTitle() string {
return random([]string{
"Unauthorized Access Attempt",
"Multiple Failed Login Attempts",
"Suspicious File Download",
"User Account Locked",
"Unusual Network Activity",
"Phishing Email Reported",
"Sensitive Data Transfer Detected",
"Malware Infection Found",
"Unauthorized Device Connected",
"Brute-Force Attack Attempt",
"Security Patch Required",
"External IP Address Probing Network",
"Suspicious Behavior Detected",
"Unauthorized Software Installation",
"Access Control System Malfunction",
"DDoS Attack Detected",
})
}
func fakeTicketDescription() string {
return random([]string{
"Unauthorized access attempt detected in the main server room.",
"Multiple failed login attempts from an unknown IP address.",
"Suspicious file download flagged by antivirus software.",
"User account locked due to repeated incorrect password entries.",
"Unusual network activity observed on the internal firewall.",
"Phishing email reported by several employees.",
"Sensitive data transfer detected outside the approved hours.",
"Malware infection found on a workstation in the finance department.",
"Unauthorized device connected to the company network.",
"Brute-force attack attempt on the admin account detected.",
"Security patch required for vulnerability in outdated software.",
"External IP address attempting to probe network ports.",
"Suspicious behavior detected by user in HR department.",
"Unauthorized software installation on company laptop.",
"Access control system malfunction at the main entrance.",
"DDoS attack detected on company web server.",
"Unusual outbound traffic to a known malicious domain.",
"Potential insider threat flagged by behavior analysis tool.",
"Compromised credentials detected on dark web.",
"Encryption key rotation required for compliance with security policy.",
})
}
func fakeTicketComment() string {
return random([]string{
"Ticket opened by user.",
"Initial investigation started.",
"Further analysis required.",
"Escalated to security team.",
"Action taken to mitigate risk.",
"Resolution in progress.",
"User notified of incident.",
"Security incident confirmed.",
"Containment measures implemented.",
"Root cause analysis underway.",
"Forensic investigation initiated.",
"Data breach confirmed.",
"Incident response team activated.",
"Legal counsel consulted.",
"Public relations notified.",
"Regulatory authorities informed.",
"Compensation plan developed.",
"Press release drafted.",
"Media monitoring in progress.",
"Post-incident review scheduled.",
})
}
func fakeTicketTimelineMessage() string {
return random([]string{
"Initial investigation started.",
"Further analysis required.",
"Escalated to security team.",
"Action taken to mitigate risk.",
"Resolution in progress.",
"User notified of incident.",
"Security incident confirmed.",
"Containment measures implemented.",
"Root cause analysis underway.",
"Forensic investigation initiated.",
"Data breach confirmed.",
"Incident response team activated.",
"Legal counsel consulted.",
"Public relations notified.",
"Regulatory authorities informed.",
"Compensation plan developed.",
"Press release drafted.",
"Media monitoring in progress.",
"Post-incident review scheduled.",
})
}
func fakeTicketTask() string {
return random([]string{
"Interview witnesses.",
"Review security camera footage.",
"Analyze network traffic logs.",
"Scan for malware on affected systems.",
"Check for unauthorized software installations.",
"Conduct vulnerability assessment.",
"Implement security patch.",
"Change firewall rules.",
"Reset compromised credentials.",
"Isolate infected systems.",
"Monitor for further suspicious activity.",
"Coordinate with law enforcement.",
"Notify affected customers.",
"Prepare incident report.",
"Update security policies.",
"Train employees on security best practices.",
"Conduct post-incident review.",
"Implement lessons learned.",
"Improve incident response procedures.",
"Enhance security awareness program.",
})
}
func random[T any](e []T) T {
return e[gofakeit.IntN(len(e))]
}

60
app/data/text_test.go Normal file
View File

@@ -0,0 +1,60 @@
package data
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_fakeTicketComment(t *testing.T) {
t.Parallel()
assert.NotEmpty(t, fakeTicketComment())
}
func Test_fakeTicketDescription(t *testing.T) {
t.Parallel()
assert.NotEmpty(t, fakeTicketDescription())
}
func Test_fakeTicketTask(t *testing.T) {
t.Parallel()
assert.NotEmpty(t, fakeTicketTask())
}
func Test_fakeTicketTimelineMessage(t *testing.T) {
t.Parallel()
assert.NotEmpty(t, fakeTicketTimelineMessage())
}
func Test_random(t *testing.T) {
t.Parallel()
type args[T any] struct {
e []T
}
type testCase[T any] struct {
name string
args args[T]
}
tests := []testCase[int]{
{
name: "Test random",
args: args[int]{e: []int{1, 2, 3, 4, 5}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := random(tt.args.e)
assert.Contains(t, tt.args.e, got)
})
}
}

216
app/data/upgradetest.go Normal file
View File

@@ -0,0 +1,216 @@
package data
import (
"context"
_ "embed"
"encoding/json"
"fmt"
"time"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
//go:embed scripts/upgradetest.py
var Script string
func GenerateUpgradeTestData(ctx context.Context, queries *sqlc.Queries) error { //nolint:cyclop
if _, err := createTestUser(ctx, queries); err != nil {
return err
}
for _, ticket := range CreateUpgradeTestDataTickets() {
_, err := queries.InsertTicket(ctx, sqlc.InsertTicketParams{
ID: ticket.ID,
Name: ticket.Name,
Type: ticket.Type,
Description: ticket.Description,
Open: ticket.Open,
Schema: ticket.Schema,
State: ticket.State,
Owner: ticket.Owner,
Resolution: ticket.Resolution,
Created: ticket.Created,
Updated: ticket.Updated,
})
if err != nil {
return fmt.Errorf("failed to create ticket: %w", err)
}
}
for _, comment := range CreateUpgradeTestDataComments() {
_, err := queries.InsertComment(ctx, sqlc.InsertCommentParams{
ID: comment.ID,
Ticket: comment.Ticket,
Author: comment.Author,
Message: comment.Message,
Created: comment.Created,
Updated: comment.Updated,
})
if err != nil {
return fmt.Errorf("failed to create comment: %w", err)
}
}
for _, timeline := range CreateUpgradeTestDataTimeline() {
_, err := queries.InsertTimeline(ctx, sqlc.InsertTimelineParams{
ID: timeline.ID,
Ticket: timeline.Ticket,
Time: timeline.Time,
Message: timeline.Message,
Created: timeline.Created,
Updated: timeline.Updated,
})
if err != nil {
return fmt.Errorf("failed to create timeline: %w", err)
}
}
for _, task := range CreateUpgradeTestDataTasks() {
_, err := queries.InsertTask(ctx, sqlc.InsertTaskParams{
ID: task.ID,
Ticket: task.Ticket,
Name: task.Name,
Open: task.Open,
Owner: task.Owner,
Created: task.Created,
Updated: task.Updated,
})
if err != nil {
return fmt.Errorf("failed to create task: %w", err)
}
}
for _, link := range CreateUpgradeTestDataLinks() {
_, err := queries.InsertLink(ctx, sqlc.InsertLinkParams{
ID: link.ID,
Ticket: link.Ticket,
Url: link.Url,
Name: link.Name,
Created: link.Created,
Updated: link.Updated,
})
if err != nil {
return fmt.Errorf("failed to create link: %w", err)
}
}
for _, reaction := range CreateUpgradeTestDataReaction() {
_, err := queries.InsertReaction(ctx, sqlc.InsertReactionParams{ //nolint: staticcheck
ID: reaction.ID,
Name: reaction.Name,
Trigger: reaction.Trigger,
Triggerdata: reaction.Triggerdata,
Action: reaction.Action,
Actiondata: reaction.Actiondata,
Created: reaction.Created,
Updated: reaction.Updated,
})
if err != nil {
return fmt.Errorf("failed to create reaction: %w", err)
}
}
return nil
}
func CreateUpgradeTestDataTickets() map[string]sqlc.Ticket {
return map[string]sqlc.Ticket{
"t_0": {
ID: "t_0",
Created: ticketCreated,
Updated: ticketCreated.Add(time.Minute * 5),
Name: "phishing-123",
Type: "alert",
Description: "Phishing email reported by several employees.",
Open: true,
Schema: json.RawMessage(`{"type":"object","properties":{"tlp":{"title":"TLP","type":"string"}}}`),
State: json.RawMessage(`{"severity":"Medium"}`),
Owner: pointer.Pointer("u_test"),
},
}
}
func CreateUpgradeTestDataComments() map[string]sqlc.Comment {
return map[string]sqlc.Comment{
"c_0": {
ID: "c_0",
Created: ticketCreated.Add(time.Minute * 10),
Updated: ticketCreated.Add(time.Minute * 15),
Ticket: "t_0",
Author: "u_test",
Message: "This is a test comment.",
},
}
}
func CreateUpgradeTestDataTimeline() map[string]sqlc.Timeline {
return map[string]sqlc.Timeline{
"tl_0": {
ID: "tl_0",
Created: ticketCreated.Add(time.Minute * 15),
Updated: ticketCreated.Add(time.Minute * 20),
Ticket: "t_0",
Time: ticketCreated.Add(time.Minute * 15),
Message: "This is a test timeline message.",
},
}
}
func CreateUpgradeTestDataTasks() map[string]sqlc.Task {
return map[string]sqlc.Task{
"ts_0": {
ID: "ts_0",
Created: ticketCreated.Add(time.Minute * 20),
Updated: ticketCreated.Add(time.Minute * 25),
Ticket: "t_0",
Name: "This is a test task.",
Open: true,
Owner: pointer.Pointer("u_test"),
},
}
}
func CreateUpgradeTestDataLinks() map[string]sqlc.Link {
return map[string]sqlc.Link{
"l_0": {
ID: "l_0",
Created: ticketCreated.Add(time.Minute * 25),
Updated: ticketCreated.Add(time.Minute * 30),
Ticket: "t_0",
Url: "https://www.example.com",
Name: "This is a test link.",
},
}
}
func CreateUpgradeTestDataReaction() map[string]sqlc.Reaction {
var (
reactionCreated = time.Date(2025, 2, 1, 11, 30, 0, 0, time.UTC)
reactionUpdated = reactionCreated.Add(time.Minute * 5)
)
createTicketActionData := marshal(map[string]any{
"requirements": "pocketbase",
"script": Script,
})
return map[string]sqlc.Reaction{
"w_0": {
ID: "w_0",
Created: reactionCreated,
Updated: reactionUpdated,
Name: "Create New Ticket",
Trigger: "schedule",
Triggerdata: json.RawMessage(`{"expression":"12 * * * *"}`),
Action: "python",
Actiondata: createTicketActionData,
},
}
}
func marshal(m map[string]any) json.RawMessage {
b, _ := json.Marshal(m) //nolint:errchkjson
return b
}

30
app/data/user.go Normal file
View File

@@ -0,0 +1,30 @@
package data
import (
"context"
"fmt"
"time"
"github.com/SecurityBrewery/catalyst/app/auth/password"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/pointer"
)
func createTestUser(ctx context.Context, queries *sqlc.Queries) (sqlc.User, error) {
passwordHash, tokenKey, err := password.Hash("1234567890")
if err != nil {
return sqlc.User{}, fmt.Errorf("failed to hash password: %w", err)
}
return queries.InsertUser(ctx, sqlc.InsertUserParams{
ID: "u_test",
Username: "u_test",
Name: pointer.Pointer("Test User"),
Email: pointer.Pointer("user@catalyst-soar.com"),
Active: true,
PasswordHash: passwordHash,
TokenKey: tokenKey,
Created: time.Now(),
Updated: time.Now(),
})
}

112
app/database/db.go Normal file
View File

@@ -0,0 +1,112 @@
package database
import (
"context"
"crypto/rand"
"database/sql"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"time"
_ "github.com/mattn/go-sqlite3" // import sqlite driver
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
const sqliteDriver = "sqlite3"
func DB(ctx context.Context, dir string) (*sqlc.Queries, func(), error) {
filename := filepath.Join(dir, "data.db")
slog.InfoContext(ctx, "Connecting to database", "path", filename)
// see https://briandouglas.ie/sqlite-defaults/ for more details
pragmas := []string{
// Enable WAL mode for better concurrency
"journal_mode=WAL",
// Enable synchronous mode for better data integrity
"synchronous=NORMAL",
// Set busy timeout to 5 seconds
"busy_timeout=5000",
// Set cache size to 20MB
"cache_size=-20000",
// Enable foreign key checks
"foreign_keys=ON",
// Enable incremental vacuuming
"auto_vacuum=INCREMENTAL",
// Set temp store to memory
"temp_store=MEMORY",
// Set mmap size to 2GB
"mmap_size=2147483648",
// Set page size to 8192
"page_size=8192",
}
_ = os.MkdirAll(filepath.Dir(filename), 0o755)
write, err := sql.Open(sqliteDriver, fmt.Sprintf("file:%s", filename))
if err != nil {
return nil, nil, fmt.Errorf("failed to open database: %w", err)
}
write.SetMaxOpenConns(1)
write.SetConnMaxIdleTime(time.Minute)
for _, pragma := range pragmas {
if _, err := write.ExecContext(ctx, fmt.Sprintf("PRAGMA %s", pragma)); err != nil {
return nil, nil, fmt.Errorf("failed to set pragma %s: %w", pragma, err)
}
}
read, err := sql.Open(sqliteDriver, fmt.Sprintf("file:%s?mode=ro", filename))
if err != nil {
return nil, nil, fmt.Errorf("failed to open database: %w", err)
}
read.SetMaxOpenConns(100)
read.SetConnMaxIdleTime(time.Minute)
queries := sqlc.New(read, write)
return queries, func() {
if err := read.Close(); err != nil {
slog.Error("failed to close read connection", "error", err)
}
if err := write.Close(); err != nil {
slog.Error("failed to close write connection", "error", err)
}
}, nil
}
func TestDB(t *testing.T, dir string) *sqlc.Queries {
queries, cleanup, err := DB(t.Context(), filepath.Join(dir, "data.db"))
require.NoError(t, err)
t.Cleanup(cleanup)
return queries
}
func GenerateID(prefix string) string {
return strings.ToLower(prefix) + randomstring(12)
}
const base32alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func randomstring(l int) string {
rand.Text()
src := make([]byte, l)
_, _ = rand.Read(src)
for i := range src {
src[i] = base32alphabet[int(src[i])%len(base32alphabet)]
}
return string(src)
}

21
app/database/db_test.go Normal file
View File

@@ -0,0 +1,21 @@
package database_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
func TestDBForeignKeyConstraints(t *testing.T) {
t.Parallel()
queries := database.TestDB(t, t.TempDir())
assert.Error(t, queries.AssignGroupToUser(t.Context(), sqlc.AssignGroupToUserParams{
UserID: "does_not_exist",
GroupID: "also_missing",
}))
}

View File

@@ -0,0 +1,236 @@
CREATE TABLE IF NOT EXISTS _migrations
(
file VARCHAR(255) PRIMARY KEY NOT NULL,
applied INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS _admins
(
id TEXT PRIMARY KEY NOT NULL,
avatar INTEGER DEFAULT 0 NOT NULL,
email TEXT UNIQUE NOT NULL,
tokenKey TEXT UNIQUE NOT NULL,
passwordHash TEXT NOT NULL,
lastResetSentAt TEXT DEFAULT "" NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS _collections
(
id TEXT PRIMARY KEY NOT NULL,
system BOOLEAN DEFAULT FALSE NOT NULL,
type TEXT DEFAULT "base" NOT NULL,
name TEXT UNIQUE NOT NULL,
schema JSON DEFAULT "[]" NOT NULL,
indexes JSON DEFAULT "[]" NOT NULL,
listRule TEXT DEFAULT NULL,
viewRule TEXT DEFAULT NULL,
createRule TEXT DEFAULT NULL,
updateRule TEXT DEFAULT NULL,
deleteRule TEXT DEFAULT NULL,
options JSON DEFAULT "{}" NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS _params
(
id TEXT PRIMARY KEY NOT NULL,
key TEXT UNIQUE NOT NULL,
value JSON DEFAULT NULL,
created TEXT DEFAULT "" NOT NULL,
updated TEXT DEFAULT "" NOT NULL
);
CREATE TABLE IF NOT EXISTS _externalAuths
(
id TEXT PRIMARY KEY NOT NULL,
collectionId TEXT NOT NULL,
recordId TEXT NOT NULL,
provider TEXT NOT NULL,
providerId TEXT NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
---
FOREIGN KEY (collectionId) REFERENCES _collections (id) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE UNIQUE INDEX IF NOT EXISTS _externalAuths_record_provider_idx on _externalAuths (collectionId, recordId, provider);
CREATE UNIQUE INDEX IF NOT EXISTS _externalAuths_collection_provider_idx on _externalAuths (collectionId, provider, providerId);
CREATE TABLE IF NOT EXISTS users
(
avatar TEXT DEFAULT '' NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
email TEXT DEFAULT '' NOT NULL,
emailVisibility BOOLEAN DEFAULT FALSE NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
lastLoginAlertSentAt TEXT DEFAULT '' NOT NULL,
lastResetSentAt TEXT DEFAULT '' NOT NULL,
lastVerificationSentAt TEXT DEFAULT '' NOT NULL,
name TEXT DEFAULT '' NOT NULL,
passwordHash TEXT NOT NULL,
tokenKey TEXT NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
username TEXT NOT NULL,
verified BOOLEAN DEFAULT FALSE NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS __pb_users_auth__username_idx ON users (username);
CREATE UNIQUE INDEX IF NOT EXISTS __pb_users_auth__email_idx ON users (email) WHERE email != '';
CREATE UNIQUE INDEX IF NOT EXISTS __pb_users_auth__tokenKey_idx ON users (tokenKey);
CREATE TABLE IF NOT EXISTS webhooks
(
collection TEXT DEFAULT '' NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
destination TEXT DEFAULT '' NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS types
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
icon TEXT DEFAULT '' NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
plural TEXT DEFAULT '' NOT NULL,
schema JSON DEFAULT NULL,
singular TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS tickets
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
description TEXT DEFAULT '' NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
open BOOLEAN DEFAULT FALSE NOT NULL,
owner TEXT DEFAULT '' NOT NULL,
resolution TEXT DEFAULT '' NOT NULL,
schema JSON DEFAULT NULL,
state JSON DEFAULT NULL,
type TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
open BOOLEAN DEFAULT FALSE NOT NULL,
owner TEXT DEFAULT '' NOT NULL,
ticket TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS comments
(
author TEXT DEFAULT '' NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
message TEXT DEFAULT '' NOT NULL,
ticket TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS timeline
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
message TEXT DEFAULT '' NOT NULL,
ticket TEXT DEFAULT '' NOT NULL,
time TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS links
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
ticket TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
url TEXT DEFAULT '' NOT NULL
);
CREATE TABLE IF NOT EXISTS files
(
blob TEXT DEFAULT '' NOT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
size NUMERIC DEFAULT 0 NOT NULL,
ticket TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE TABLE IF NOT EXISTS features
(
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS unique_name ON features (name);
CREATE VIEW IF NOT EXISTS sidebar AS
SELECT types.id as id,
types.singular as singular,
types.plural as plural,
types.icon as icon,
(SELECT COUNT(tickets.id) FROM tickets WHERE tickets.type = types.id AND tickets.open = true) as count
FROM types
ORDER BY types.plural;
CREATE TABLE IF NOT EXISTS reactions
(
action TEXT DEFAULT '' NOT NULL,
actiondata JSON DEFAULT NULL,
created TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL,
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT DEFAULT '' NOT NULL,
trigger TEXT DEFAULT '' NOT NULL,
triggerdata JSON DEFAULT NULL,
updated TEXT DEFAULT (strftime('%Y-%m-%d %H:%M:%fZ')) NOT NULL
);
CREATE VIEW IF NOT EXISTS ticket_search AS
SELECT tickets.id,
tickets.name,
tickets.created,
tickets.description,
tickets.open,
tickets.type,
tickets.state,
users.name as owner_name,
group_concat(comments.message
) as comment_messages,
group_concat(files.name
) as file_names,
group_concat(links.name
) as link_names,
group_concat(links.url
) as link_urls,
group_concat(tasks.name
) as task_names,
group_concat(timeline.message
) as timeline_messages
FROM tickets
LEFT JOIN comments ON comments.ticket = tickets.id
LEFT JOIN files ON files.ticket = tickets.id
LEFT JOIN links ON links.ticket = tickets.id
LEFT JOIN tasks ON tasks.ticket = tickets.id
LEFT JOIN timeline ON timeline.ticket = tickets.id
LEFT JOIN users ON users.id = tickets.owner
GROUP BY tickets.id;
CREATE VIEW IF NOT EXISTS dashboard_counts AS
SELECT id, count
FROM (SELECT 'users' as id,
COUNT(users.id
) as count
FROM users
UNION
SELECT 'tickets' as id,
COUNT(tickets.id
) as count
FROM tickets
UNION
SELECT 'tasks' as id,
COUNT(tasks.id
) as count
FROM tasks
UNION
SELECT 'reactions' as id,
COUNT(reactions.id
) as count
FROM reactions) as counts;

View File

@@ -0,0 +1,390 @@
DROP TABLE _migrations;
DROP TABLE _collections;
DROP TABLE _externalauths;
DROP VIEW sidebar;
DROP VIEW ticket_search;
DROP VIEW dashboard_counts;
--- _params
CREATE TABLE new_params
(
key TEXT PRIMARY KEY NOT NULL,
value JSON
);
INSERT INTO new_params
(key, value)
SELECT key, value
FROM _params;
DROP TABLE _params;
ALTER TABLE new_params
RENAME TO _params;
--- users
CREATE TABLE new_users
(
id TEXT PRIMARY KEY DEFAULT ('u' || lower(hex(randomblob(7)))) NOT NULL,
username TEXT NOT NULL,
passwordHash TEXT NOT NULL,
tokenKey TEXT NOT NULL,
active BOOLEAN NOT NULL,
name TEXT,
email TEXT,
avatar TEXT,
lastresetsentat DATETIME,
lastverificationsentat DATETIME,
admin BOOLEAN NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
INSERT INTO new_users
(avatar, email, id, lastresetsentat, lastverificationsentat, name, passwordHash, tokenKey, username, active, admin,
created,
updated)
SELECT avatar,
email,
id,
lastResetSentAt,
lastVerificationSentAt,
name,
passwordHash,
tokenKey,
username,
verified,
false,
created,
updated
FROM users;
INSERT INTO new_users
(avatar, email, id, lastresetsentat, lastverificationsentat, name, passwordHash, tokenKey, username, active, admin,
created,
updated)
SELECT avatar,
email,
id,
lastResetSentAt,
'',
email,
passwordHash,
tokenKey,
id,
true,
true,
created,
updated
FROM _admins;
DROP TABLE users;
DROP TABLE _admins;
ALTER TABLE new_users
RENAME TO users;
--- webhooks
CREATE TABLE new_webhooks
(
id TEXT PRIMARY KEY DEFAULT ('w' || lower(hex(randomblob(7)))) NOT NULL,
collection TEXT NOT NULL,
destination TEXT NOT NULL,
name TEXT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
INSERT INTO new_webhooks
(collection, destination, id, name, created, updated)
SELECT collection, destination, id, name, datetime(created), datetime(updated)
FROM webhooks;
DROP TABLE webhooks;
ALTER TABLE new_webhooks
RENAME TO webhooks;
--- types
CREATE TABLE new_types
(
id TEXT PRIMARY KEY DEFAULT ('y' || lower(hex(randomblob(7)))) NOT NULL,
icon TEXT,
singular TEXT NOT NULL,
plural TEXT NOT NULL,
schema JSON,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
INSERT INTO new_types
(id, icon, singular, plural, schema, created, updated)
SELECT id, icon, singular, plural, schema, created, updated
FROM types;
DROP TABLE types;
ALTER TABLE new_types
RENAME TO types;
--- ticket
CREATE TABLE new_tickets
(
id TEXT PRIMARY KEY DEFAULT ('t' || lower(hex(randomblob(7)))) NOT NULL,
type TEXT NOT NULL,
owner TEXT,
name TEXT NOT NULL,
description TEXT NOT NULL,
open BOOLEAN NOT NULL,
resolution TEXT,
schema JSON,
state JSON,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (type) REFERENCES types (id) ON DELETE SET NULL,
FOREIGN KEY (owner) REFERENCES users (id) ON DELETE SET NULL
);
INSERT INTO new_tickets
(id, name, description, open, owner, resolution, schema, state, type, created, updated)
SELECT id,
name,
description,
open,
owner,
resolution,
schema,
state,
type,
created,
updated
FROM tickets;
DROP TABLE tickets;
ALTER TABLE new_tickets
RENAME TO tickets;
--- tasks
CREATE TABLE new_tasks
(
id TEXT PRIMARY KEY DEFAULT ('t' || lower(hex(randomblob(7)))) NOT NULL,
ticket TEXT NOT NULL,
owner TEXT,
name TEXT NOT NULL,
open BOOLEAN NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (ticket) REFERENCES tickets (id) ON DELETE CASCADE,
FOREIGN KEY (owner) REFERENCES users (id) ON DELETE SET NULL
);
INSERT INTO new_tasks
(id, ticket, owner, name, open, created, updated)
SELECT id, ticket, owner, name, open, created, updated
FROM tasks;
DROP TABLE tasks;
ALTER TABLE new_tasks
RENAME TO tasks;
--- comments
CREATE TABLE new_comments
(
id TEXT PRIMARY KEY DEFAULT ('c' || lower(hex(randomblob(7)))) NOT NULL,
ticket TEXT NOT NULL,
author TEXT NOT NULL,
message TEXT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (ticket) REFERENCES tickets (id) ON DELETE CASCADE,
FOREIGN KEY (author) REFERENCES users (id) ON DELETE SET NULL
);
INSERT INTO new_comments
(id, ticket, author, message, created, updated)
SELECT id, ticket, author, message, created, updated
FROM comments;
DROP TABLE comments;
ALTER TABLE new_comments
RENAME TO comments;
--- timeline
CREATE TABLE new_timeline
(
id TEXT PRIMARY KEY DEFAULT ('h' || lower(hex(randomblob(7)))) NOT NULL,
ticket TEXT NOT NULL,
message TEXT NOT NULL,
time DATETIME NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (ticket) REFERENCES tickets (id) ON DELETE CASCADE
);
INSERT INTO new_timeline
(id, ticket, message, time, created, updated)
SELECT id, ticket, message, time, created, updated
FROM timeline;
DROP TABLE timeline;
ALTER TABLE new_timeline
RENAME TO timeline;
--- links
CREATE TABLE new_links
(
id TEXT PRIMARY KEY DEFAULT ('l' || lower(hex(randomblob(7)))) NOT NULL,
ticket TEXT NOT NULL,
name TEXT NOT NULL,
url TEXT NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (ticket) REFERENCES tickets (id) ON DELETE CASCADE
);
INSERT INTO new_links
(id, ticket, name, url, created, updated)
SELECT id, ticket, name, url, datetime(created), datetime(updated)
FROM links;
DROP TABLE links;
ALTER TABLE new_links
RENAME TO links;
--- files
CREATE TABLE new_files
(
id TEXT PRIMARY KEY DEFAULT ('b' || lower(hex(randomblob(7)))) NOT NULL,
ticket TEXT NOT NULL,
name TEXT NOT NULL,
blob TEXT NOT NULL,
size NUMERIC NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (ticket) REFERENCES tickets (id) ON DELETE CASCADE
);
INSERT INTO new_files
(id, name, blob, size, ticket, created, updated)
SELECT id, name, blob, size, ticket, created, updated
FROM files;
DROP TABLE files;
ALTER TABLE new_files
RENAME TO files;
--- features
CREATE TABLE new_features
(
key TEXT PRIMARY KEY NOT NULL
);
INSERT INTO new_features
(key)
SELECT name
FROM features;
DROP TABLE features;
ALTER TABLE new_features
RENAME TO features;
--- reactions
CREATE TABLE new_reactions
(
id TEXT PRIMARY KEY DEFAULT ('r' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT NOT NULL,
action TEXT NOT NULL,
actiondata JSON NOT NULL,
trigger TEXT NOT NULL,
triggerdata JSON NOT NULL,
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
INSERT INTO new_reactions
(id, name, action, actiondata, trigger, triggerdata, created, updated)
SELECT id,
name,
action,
actionData,
trigger,
triggerData,
created,
updated
FROM reactions;
DROP TABLE reactions;
ALTER TABLE new_reactions
RENAME TO reactions;
--- views
CREATE VIEW sidebar AS
SELECT types.id as id,
types.singular as singular,
types.plural as plural,
types.icon as icon,
(SELECT COUNT(tickets.id) FROM tickets WHERE tickets.type = types.id AND tickets.open = true) as count
FROM types
ORDER BY types.plural;
CREATE VIEW ticket_search AS
SELECT tickets.id,
tickets.name,
tickets.created,
tickets.description,
tickets.open,
tickets.type,
tickets.state,
users.name as owner_name,
group_concat(comments.message
) as comment_messages,
group_concat(files.name
) as file_names,
group_concat(links.name
) as link_names,
group_concat(links.url
) as link_urls,
group_concat(tasks.name
) as task_names,
group_concat(timeline.message
) as timeline_messages
FROM tickets
LEFT JOIN comments ON comments.ticket = tickets.id
LEFT JOIN files ON files.ticket = tickets.id
LEFT JOIN links ON links.ticket = tickets.id
LEFT JOIN tasks ON tasks.ticket = tickets.id
LEFT JOIN timeline ON timeline.ticket = tickets.id
LEFT JOIN users ON users.id = tickets.owner
GROUP BY tickets.id;
CREATE VIEW dashboard_counts AS
SELECT id, count
FROM (SELECT 'users' as id,
COUNT(users.id
) as count
FROM users
UNION
SELECT 'tickets' as id,
COUNT(tickets.id
) as count
FROM tickets
UNION
SELECT 'tasks' as id,
COUNT(tasks.id
) as count
FROM tasks
UNION
SELECT 'reactions' as id,
COUNT(reactions.id
) as count
FROM reactions) as counts;

View File

@@ -0,0 +1,5 @@
INSERT OR IGNORE INTO types (id, singular, plural, icon, schema) VALUES ('alert', 'Alert', 'Alerts', 'AlertTriangle', '{"type": "object", "properties": { "severity": { "title": "Severity", "enum": ["Low", "Medium", "High"]}}, "required": ["severity"]}');
INSERT OR IGNORE INTO types (id, singular, plural, icon, schema) VALUES ('incident', 'Incident', 'Incidents', 'Flame', '{"type": "object", "properties": { "severity": { "title": "Severity", "enum": ["Low", "Medium", "High"]}}, "required": ["severity"]}');
INSERT OR IGNORE INTO types (id, singular, plural, icon, schema) VALUES ('vulnerability', 'Vulnerability', 'Vulnerabilities', 'Bug', '{"type": "object", "properties": { "severity": { "title": "Severity", "enum": ["Low", "Medium", "High"]}}, "required": ["severity"]}');
INSERT OR IGNORE INTO users (id, name, username, passwordHash, tokenKey, active, admin) VALUES ('system', 'System', 'system', '', lower(hex(randomblob(26))), true, true);

View File

@@ -0,0 +1,82 @@
CREATE TABLE groups
(
id TEXT PRIMARY KEY DEFAULT ('g' || lower(hex(randomblob(7)))) NOT NULL,
name TEXT UNIQUE NOT NULL,
permissions TEXT NOT NULL, -- JSON array string like '["read:article","write:article"]'
created DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE TABLE user_groups
(
user_id TEXT NOT NULL,
group_id TEXT NOT NULL,
PRIMARY KEY (user_id, group_id),
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES groups (id) ON DELETE CASCADE
);
CREATE TABLE group_inheritance
(
parent_group_id TEXT NOT NULL,
child_group_id TEXT NOT NULL,
PRIMARY KEY (parent_group_id, child_group_id),
FOREIGN KEY (parent_group_id) REFERENCES groups (id) ON DELETE CASCADE,
FOREIGN KEY (child_group_id) REFERENCES groups (id) ON DELETE CASCADE
);
CREATE VIEW group_effective_groups AS
WITH RECURSIVE all_groups(child_group_id, parent_group_id, group_type)
AS (SELECT rr.child_group_id, rr.parent_group_id, 'direct' AS group_type
FROM group_inheritance rr
UNION
SELECT ar.child_group_id, ri.parent_group_id, 'indirect' AS group_type
FROM all_groups ar
JOIN group_inheritance ri ON ri.child_group_id = ar.parent_group_id)
SELECT child_group_id, parent_group_id, group_type
FROM all_groups;
CREATE VIEW group_effective_permissions AS
SELECT re.parent_group_id, CAST(json_each.value AS TEXT) AS permission
FROM group_effective_groups re
JOIN groups r ON r.id = re.child_group_id, json_each(r.permissions);
CREATE VIEW user_effective_groups AS
WITH RECURSIVE all_groups(user_id, group_id, group_type) AS (
-- Direct groups
SELECT ur.user_id, ur.group_id, 'direct' AS group_type
FROM user_groups ur
UNION
-- Inherited groups
SELECT ar.user_id, ri.child_group_id, 'indirect' AS group_type
FROM all_groups ar
JOIN group_inheritance ri ON ri.parent_group_id = ar.group_id)
SELECT user_id,
group_id,
group_type
FROM all_groups;
CREATE VIEW user_effective_permissions AS
SELECT DISTINCT uer.user_id,
CAST(json_each.value AS TEXT) AS permission
FROM user_effective_groups uer
JOIN groups r ON r.id = uer.group_id, json_each(r.permissions);
INSERT INTO groups (id, name, permissions)
VALUES ('analyst', 'Analyst', '["type:read", "file:read", "ticket:read", "ticket:write", "user:read", "group:read"]'),
('admin', 'Admin', '["admin"]');
INSERT INTO user_groups (user_id, group_id)
SELECT id, 'analyst'
FROM users
WHERE NOT admin;
INSERT INTO user_groups (user_id, group_id)
SELECT id, 'admin'
FROM users
WHERE admin;
ALTER TABLE users
DROP COLUMN admin;

View File

@@ -0,0 +1,6 @@
package migrations
import "embed"
//go:embed *.sql
var Migrations embed.FS

52
app/database/paginate.go Normal file
View File

@@ -0,0 +1,52 @@
package database
import (
"context"
"database/sql"
"errors"
)
func Paginate(ctx context.Context, f func(ctx context.Context, offset, limit int64) (nextPage bool, err error)) error {
const pageSize int64 = 100
for i := range int64(1000) {
nextPage, err := f(ctx, i*pageSize, pageSize)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// No more features to process, exit the loop
return nil
}
return err
}
if !nextPage {
return nil
}
}
return errors.New("pagination limit reached, too many pages")
}
func PaginateItems[T any](ctx context.Context, f func(ctx context.Context, offset, limit int64) (items []T, err error)) ([]T, error) {
var allItems []T
if err := Paginate(ctx, func(ctx context.Context, offset, limit int64) (nextPage bool, err error) {
items, err := f(ctx, offset, limit)
if err != nil {
return false, err
}
if len(items) == 0 {
return false, nil
}
allItems = append(allItems, items...)
return true, nil
}); err != nil {
return nil, err
}
return allItems, nil
}

View File

@@ -0,0 +1,97 @@
package database
import (
"context"
"database/sql"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPaginate_AllPages(t *testing.T) {
t.Parallel()
calls := 0
err := Paginate(t.Context(), func(_ context.Context, _, _ int64) (bool, error) {
calls++
if calls < 3 {
return true, nil
}
return false, nil
})
require.NoError(t, err, "expected no error")
assert.Equal(t, 3, calls, "expected 3 calls")
}
func TestPaginate_EarlyStop(t *testing.T) {
t.Parallel()
calls := 0
err := Paginate(t.Context(), func(_ context.Context, _, _ int64) (bool, error) {
calls++
return false, nil
})
require.NoError(t, err, "expected no error")
assert.Equal(t, 1, calls, "expected 1 call")
}
func TestPaginate_Error(t *testing.T) {
t.Parallel()
errTest := errors.New("fail")
err := Paginate(t.Context(), func(_ context.Context, _, _ int64) (bool, error) {
return false, errTest
})
assert.ErrorIs(t, err, errTest, "expected error")
}
func TestPaginate_NoRows(t *testing.T) {
t.Parallel()
err := Paginate(t.Context(), func(_ context.Context, _, _ int64) (bool, error) {
return false, sql.ErrNoRows
})
require.NoError(t, err, "expected no error")
}
func TestPaginateItems(t *testing.T) {
t.Parallel()
calls := 0
f := func(_ context.Context, offset, _ int64) ([]int, error) {
calls++
if offset >= 100 {
return nil, sql.ErrNoRows
}
return []int{1}, nil
}
items, err := PaginateItems(t.Context(), f)
require.NoError(t, err, "expected no error")
assert.Equal(t, []int{1}, items, "expected items to match")
assert.Equal(t, 2, calls, "expected 2 calls")
}
func TestPaginateItemsLarge(t *testing.T) {
t.Parallel()
calls := 0
f := func(_ context.Context, offset, _ int64) ([]int, error) {
calls++
if offset >= 200 {
return nil, sql.ErrNoRows
}
return []int{1}, nil
}
items, err := PaginateItems(t.Context(), f)
require.NoError(t, err, "expected no error")
assert.Equal(t, []int{1, 1}, items, "expected items to match")
assert.Equal(t, 3, calls, "expected 3 calls")
}

285
app/database/read.sql Normal file
View File

@@ -0,0 +1,285 @@
-- name: Param :one
SELECT *
FROM _params
WHERE _params.key = @key;
-------------------------------------------------------------------
-- name: Ticket :one
SELECT tickets.*, users.name as owner_name, types.singular as type_singular, types.plural as type_plural
FROM tickets
LEFT JOIN users ON users.id = tickets.owner
LEFT JOIN types ON types.id = tickets.type
WHERE tickets.id = @id;
-- name: ListTickets :many
SELECT tickets.*,
users.name as owner_name,
types.singular as type_singular,
types.plural as type_plural,
COUNT(*) OVER () as total_count
FROM tickets
LEFT JOIN users ON users.id = tickets.owner
LEFT JOIN types ON types.id = tickets.type
ORDER BY tickets.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetComment :one
SELECT comments.*, users.name as author_name
FROM comments
LEFT JOIN users ON users.id = comments.author
WHERE comments.id = @id;
-- name: ListComments :many
SELECT comments.*, users.name as author_name, COUNT(*) OVER () as total_count
FROM comments
LEFT JOIN users ON users.id = comments.author
WHERE ticket = @ticket
OR @ticket = ''
ORDER BY comments.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetFeature :one
SELECT *
FROM features
WHERE key = @key;
-- name: ListFeatures :many
SELECT features.*, COUNT(*) OVER () as total_count
FROM features
ORDER BY features.key DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetFile :one
SELECT *
FROM files
WHERE id = @id;
-- name: ListFiles :many
SELECT files.*, COUNT(*) OVER () as total_count
FROM files
WHERE ticket = @ticket
OR @ticket = ''
ORDER BY files.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetLink :one
SELECT *
FROM links
WHERE id = @id;
-- name: ListLinks :many
SELECT links.*, COUNT(*) OVER () as total_count
FROM links
WHERE ticket = @ticket
OR @ticket = ''
ORDER BY links.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetReaction :one
SELECT *
FROM reactions
WHERE id = @id;
-- name: ListReactions :many
SELECT reactions.*, COUNT(*) OVER () as total_count
FROM reactions
ORDER BY reactions.created DESC
LIMIT @limit OFFSET @offset;
-- name: ListReactionsByTrigger :many
SELECT reactions.*, COUNT(*) OVER () as total_count
FROM reactions
WHERE trigger = @trigger
ORDER BY reactions.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetTask :one
SELECT tasks.*, users.name as owner_name, tickets.name as ticket_name, tickets.type as ticket_type
FROM tasks
LEFT JOIN users ON users.id = tasks.owner
LEFT JOIN tickets ON tickets.id = tasks.ticket
WHERE tasks.id = @id;
-- name: ListTasks :many
SELECT tasks.*,
users.name as owner_name,
tickets.name as ticket_name,
tickets.type as ticket_type,
COUNT(*) OVER () as total_count
FROM tasks
LEFT JOIN users ON users.id = tasks.owner
LEFT JOIN tickets ON tickets.id = tasks.ticket
WHERE ticket = @ticket
OR @ticket = ''
ORDER BY tasks.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetTimeline :one
SELECT *
FROM timeline
WHERE id = @id;
-- name: ListTimeline :many
SELECT timeline.*, COUNT(*) OVER () as total_count
FROM timeline
WHERE ticket = @ticket
OR @ticket = ''
ORDER BY timeline.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetType :one
SELECT *
FROM types
WHERE id = @id;
-- name: ListTypes :many
SELECT types.*, COUNT(*) OVER () as total_count
FROM types
ORDER BY created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetUser :one
SELECT *
FROM users
WHERE id = @id;
-- name: UserByUserName :one
SELECT *
FROM users
WHERE username = @username;
-- name: UserByEmail :one
SELECT *
FROM users
WHERE email = @email;
-- name: SystemUser :one
SELECT *
FROM users
WHERE id = 'system';
-- name: ListUsers :many
SELECT users.*, COUNT(*) OVER () as total_count
FROM users
WHERE id != 'system'
ORDER BY users.created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetWebhook :one
SELECT *
FROM webhooks
WHERE id = @id;
-- name: ListWebhooks :many
SELECT webhooks.*, COUNT(*) OVER () as total_count
FROM webhooks
ORDER BY created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetDashboardCounts :many
SELECT *
FROM dashboard_counts;
-- name: GetSidebar :many
SELECT *
FROM sidebar;
-- name: SearchTickets :many
SELECT id,
name,
created,
description,
open,
type,
state,
owner_name,
COUNT(*) OVER () as total_count
FROM ticket_search
WHERE (@query = '' OR (name LIKE '%' || @query || '%'
OR description LIKE '%' || @query || '%'
OR comment_messages LIKE '%' || @query || '%'
OR file_names LIKE '%' || @query || '%'
OR link_names LIKE '%' || @query || '%'
OR link_urls LIKE '%' || @query || '%'
OR task_names LIKE '%' || @query || '%'
OR timeline_messages LIKE '%' || @query || '%'))
AND (sqlc.narg('type') IS NULL OR type = sqlc.narg('type'))
AND (sqlc.narg('open') IS NULL OR open = sqlc.narg('open'))
ORDER BY created DESC
LIMIT @limit OFFSET @offset;
------------------------------------------------------------------
-- name: GetGroup :one
SELECT *
FROM groups
WHERE id = @id;
-- name: ListGroups :many
SELECT g.*, COUNT(*) OVER () as total_count
FROM groups AS g
ORDER BY g.created DESC
LIMIT @limit OFFSET @offset;
-- name: ListUserGroups :many
SELECT g.*, uer.group_type, COUNT(*) OVER () as total_count
FROM user_effective_groups uer
JOIN groups AS g ON g.id = uer.group_id
WHERE uer.user_id = @user_id
ORDER BY g.name DESC;
-- name: ListGroupUsers :many
SELECT users.*, uer.group_type
FROM user_effective_groups uer
JOIN users ON users.id = uer.user_id
WHERE uer.group_id = @group_id
ORDER BY users.name DESC;
-- name: ListUserPermissions :many
SELECT user_effective_permissions.permission
FROM user_effective_permissions
WHERE user_id = @user_id
ORDER BY permission;
-- name: ListParentGroups :many
SELECT g.*, group_effective_groups.group_type
FROM group_effective_groups
JOIN groups AS g ON g.id = group_effective_groups.child_group_id
WHERE parent_group_id = @group_id
ORDER BY group_effective_groups.group_type;
-- name: ListChildGroups :many
SELECT g.*, group_effective_groups.group_type
FROM group_effective_groups
JOIN groups AS g ON g.id = group_effective_groups.parent_group_id
WHERE child_group_id = @group_id
ORDER BY group_effective_groups.group_type;
-- name: ListParentPermissions :many
SELECT group_effective_permissions.permission
FROM group_effective_permissions
WHERE parent_group_id = @group_id
ORDER BY permission;

View File

@@ -0,0 +1,38 @@
package sqlc
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
type Queries struct {
*ReadQueries
*WriteQueries
ReadDB *sql.DB
WriteDB *sql.DB
}
type ReadQueries struct {
db DBTX
}
type WriteQueries struct {
db DBTX
}
func New(readDB, writeDB *sql.DB) *Queries {
return &Queries{
ReadQueries: &ReadQueries{db: readDB},
WriteQueries: &WriteQueries{db: writeDB},
ReadDB: readDB,
WriteDB: writeDB,
}
}

32
app/database/sqlc.yaml Normal file
View File

@@ -0,0 +1,32 @@
version: "2"
sql:
- engine: "sqlite"
queries: "read.sql"
schema: "migrations"
gen:
go:
package: "sqlc"
out: "sqlc"
emit_json_tags: true
emit_pointers_for_null_types: true
overrides:
- { "column": "*.schema", "go_type": { "type": "[]byte" } }
- { "column": "*.state", "go_type": { "type": "[]byte" } }
- { "column": "reactions.actiondata", "go_type": { "type": "[]byte" } }
- { "column": "reactions.triggerdata", "go_type": { "type": "[]byte" } }
- { "column": "_params.value", "go_type": { "type": "[]byte" } }
- engine: "sqlite"
queries: "write.sql"
schema: "migrations"
gen:
go:
package: "sqlc"
out: "sqlc"
emit_json_tags: true
emit_pointers_for_null_types: true
overrides:
- { "column": "*.schema", "go_type": { "type": "[]byte" } }
- { "column": "*.state", "go_type": { "type": "[]byte" } }
- { "column": "reactions.actiondata", "go_type": { "type": "[]byte" } }
- { "column": "reactions.triggerdata", "go_type": { "type": "[]byte" } }
- { "column": "_params.value", "go_type": { "type": "[]byte" } }

38
app/database/sqlc/db.go Normal file
View File

@@ -0,0 +1,38 @@
package sqlc
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
PrepareContext(ctx context.Context, query string) (*sql.Stmt, error)
QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error)
QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row
}
type Queries struct {
*ReadQueries
*WriteQueries
ReadDB *sql.DB
WriteDB *sql.DB
}
type ReadQueries struct {
db DBTX
}
type WriteQueries struct {
db DBTX
}
func New(readDB, writeDB *sql.DB) *Queries {
return &Queries{
ReadQueries: &ReadQueries{db: readDB},
WriteQueries: &WriteQueries{db: writeDB},
ReadDB: readDB,
WriteDB: writeDB,
}
}

194
app/database/sqlc/models.go Normal file
View File

@@ -0,0 +1,194 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package sqlc
import (
"time"
)
type Comment struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Author string `json:"author"`
Message string `json:"message"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type DashboardCount struct {
ID string `json:"id"`
Count int64 `json:"count"`
}
type Feature struct {
Key string `json:"key"`
}
type File struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Name string `json:"name"`
Blob string `json:"blob"`
Size float64 `json:"size"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Group struct {
ID string `json:"id"`
Name string `json:"name"`
Permissions string `json:"permissions"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type GroupEffectiveGroup struct {
ChildGroupID string `json:"child_group_id"`
ParentGroupID string `json:"parent_group_id"`
GroupType string `json:"group_type"`
}
type GroupEffectivePermission struct {
ParentGroupID string `json:"parent_group_id"`
Permission string `json:"permission"`
}
type GroupInheritance struct {
ParentGroupID string `json:"parent_group_id"`
ChildGroupID string `json:"child_group_id"`
}
type Link struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Name string `json:"name"`
Url string `json:"url"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Param struct {
Key string `json:"key"`
Value []byte `json:"value"`
}
type Reaction struct {
ID string `json:"id"`
Name string `json:"name"`
Action string `json:"action"`
Actiondata []byte `json:"actiondata"`
Trigger string `json:"trigger"`
Triggerdata []byte `json:"triggerdata"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Sidebar struct {
ID string `json:"id"`
Singular string `json:"singular"`
Plural string `json:"plural"`
Icon *string `json:"icon"`
Count int64 `json:"count"`
}
type Task struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Owner *string `json:"owner"`
Name string `json:"name"`
Open bool `json:"open"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Ticket struct {
ID string `json:"id"`
Type string `json:"type"`
Owner *string `json:"owner"`
Name string `json:"name"`
Description string `json:"description"`
Open bool `json:"open"`
Resolution *string `json:"resolution"`
Schema []byte `json:"schema"`
State []byte `json:"state"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type TicketSearch struct {
ID string `json:"id"`
Name string `json:"name"`
Created time.Time `json:"created"`
Description string `json:"description"`
Open bool `json:"open"`
Type string `json:"type"`
State []byte `json:"state"`
OwnerName *string `json:"owner_name"`
CommentMessages string `json:"comment_messages"`
FileNames string `json:"file_names"`
LinkNames string `json:"link_names"`
LinkUrls string `json:"link_urls"`
TaskNames string `json:"task_names"`
TimelineMessages string `json:"timeline_messages"`
}
type Timeline struct {
ID string `json:"id"`
Ticket string `json:"ticket"`
Message string `json:"message"`
Time time.Time `json:"time"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type Type struct {
ID string `json:"id"`
Icon *string `json:"icon"`
Singular string `json:"singular"`
Plural string `json:"plural"`
Schema []byte `json:"schema"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Passwordhash string `json:"passwordhash"`
Tokenkey string `json:"tokenkey"`
Active bool `json:"active"`
Name *string `json:"name"`
Email *string `json:"email"`
Avatar *string `json:"avatar"`
Lastresetsentat *time.Time `json:"lastresetsentat"`
Lastverificationsentat *time.Time `json:"lastverificationsentat"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type UserEffectiveGroup struct {
UserID string `json:"user_id"`
GroupID string `json:"group_id"`
GroupType string `json:"group_type"`
}
type UserEffectivePermission struct {
UserID string `json:"user_id"`
Permission string `json:"permission"`
}
type UserGroup struct {
UserID string `json:"user_id"`
GroupID string `json:"group_id"`
}
type Webhook struct {
ID string `json:"id"`
Collection string `json:"collection"`
Destination string `json:"destination"`
Name string `json:"name"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

45
app/database/tables.go Normal file
View File

@@ -0,0 +1,45 @@
package database
type Table struct {
ID string `json:"id"`
Name string `json:"name"`
}
var (
TicketsTable = Table{ID: "tickets", Name: "Tickets"}
CommentsTable = Table{ID: "comments", Name: "Comments"}
LinksTable = Table{ID: "links", Name: "Links"}
TasksTable = Table{ID: "tasks", Name: "Tasks"}
TimelinesTable = Table{ID: "timeline", Name: "Timeline"}
FilesTable = Table{ID: "files", Name: "Files"}
TypesTable = Table{ID: "types", Name: "Types"}
UsersTable = Table{ID: "users", Name: "Users"}
GroupsTable = Table{ID: "groups", Name: "Groups"}
ReactionsTable = Table{ID: "reactions", Name: "Reactions"}
WebhooksTable = Table{ID: "webhooks", Name: "Webhooks"}
DashboardCountsTable = Table{ID: "dashboard_counts", Name: "Dashboard Counts"}
SidebarTable = Table{ID: "sidebar", Name: "Sidebar"}
UserPermissionTable = Table{ID: "user_permissions", Name: "User Permissions"}
UserGroupTable = Table{ID: "user_groups", Name: "User Groups"}
GroupUserTable = Table{ID: "group_users", Name: "Group Users"}
GroupPermissionTable = Table{ID: "group_permissions", Name: "Group Permissions"}
GroupParentTable = Table{ID: "group_parents", Name: "Group Parents"}
GroupChildTable = Table{ID: "group_children", Name: "Group Children"}
CreateAction = "create"
UpdateAction = "update"
DeleteAction = "delete"
)
func Tables() []Table {
return []Table{
TicketsTable,
FilesTable,
TypesTable,
UsersTable,
GroupsTable,
ReactionsTable,
WebhooksTable,
}
}

328
app/database/write.sql Normal file
View File

@@ -0,0 +1,328 @@
-- name: CreateParam :exec
INSERT INTO _params (key, value)
VALUES (@key, @value)
RETURNING *;
-- name: UpdateParam :exec
UPDATE _params
SET value = @value
WHERE key = @key
RETURNING *;
------------------------------------------------------------------
-- name: InsertTicket :one
INSERT INTO tickets (id, name, description, open, owner, resolution, schema, state, type, created, updated)
VALUES (@id, @name, @description, @open, @owner, @resolution, @schema, @state, @type, @created, @updated)
RETURNING *;
-- name: CreateTicket :one
INSERT INTO tickets (name, description, open, owner, resolution, schema, state, type)
VALUES (@name, @description, @open, @owner, @resolution, @schema, @state, @type)
RETURNING *;
-- name: UpdateTicket :one
UPDATE tickets
SET name = coalesce(sqlc.narg('name'), name),
description = coalesce(sqlc.narg('description'), description),
open = coalesce(sqlc.narg('open'), open),
owner = coalesce(sqlc.narg('owner'), owner),
resolution = coalesce(sqlc.narg('resolution'), resolution),
schema = coalesce(sqlc.narg('schema'), schema),
state = coalesce(sqlc.narg('state'), state),
type = coalesce(sqlc.narg('type'), type)
WHERE id = @id
RETURNING *;
-- name: DeleteTicket :exec
DELETE
FROM tickets
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertComment :one
INSERT INTO comments (id, author, message, ticket, created, updated)
VALUES (@id, @author, @message, @ticket, @created, @updated)
RETURNING *;
-- name: CreateComment :one
INSERT INTO comments (author, message, ticket)
VALUES (@author, @message, @ticket)
RETURNING *;
-- name: UpdateComment :one
UPDATE comments
SET message = coalesce(sqlc.narg('message'), message)
WHERE id = @id
RETURNING *;
-- name: DeleteComment :exec
DELETE
FROM comments
WHERE id = @id;
------------------------------------------------------------------
-- name: CreateFeature :one
INSERT INTO features (key)
VALUES (@key)
RETURNING *;
-- name: DeleteFeature :exec
DELETE
FROM features
WHERE key = @key;
------------------------------------------------------------------
-- name: InsertFile :one
INSERT INTO files (id, name, blob, size, ticket, created, updated)
VALUES (@id, @name, @blob, @size, @ticket, @created, @updated)
RETURNING *;
-- name: CreateFile :one
INSERT INTO files (name, blob, size, ticket)
VALUES (@name, @blob, @size, @ticket)
RETURNING *;
-- name: UpdateFile :one
UPDATE files
SET name = coalesce(sqlc.narg('name'), name),
blob = coalesce(sqlc.narg('blob'), blob),
size = coalesce(sqlc.narg('size'), size)
WHERE id = @id
RETURNING *;
-- name: DeleteFile :exec
DELETE
FROM files
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertLink :one
INSERT INTO links (id, name, url, ticket, created, updated)
VALUES (@id, @name, @url, @ticket, @created, @updated)
RETURNING *;
-- name: CreateLink :one
INSERT INTO links (name, url, ticket)
VALUES (@name, @url, @ticket)
RETURNING *;
-- name: UpdateLink :one
UPDATE links
SET name = coalesce(sqlc.narg('name'), name),
url = coalesce(sqlc.narg('url'), url)
WHERE id = @id
RETURNING *;
-- name: DeleteLink :exec
DELETE
FROM links
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertReaction :one
INSERT INTO reactions (id, name, action, actiondata, trigger, triggerdata, created, updated)
VALUES (@id, @name, @action, @actiondata, @trigger, @triggerdata, @created, @updated)
RETURNING *;
-- name: CreateReaction :one
INSERT INTO reactions (name, action, actiondata, trigger, triggerdata)
VALUES (@name, @action, @actiondata, @trigger, @triggerdata)
RETURNING *;
-- name: UpdateReaction :one
UPDATE reactions
SET name = coalesce(sqlc.narg('name'), name),
action = coalesce(sqlc.narg('action'), action),
actiondata = coalesce(sqlc.narg('actiondata'), actiondata),
trigger = coalesce(sqlc.narg('trigger'), trigger),
triggerdata = coalesce(sqlc.narg('triggerdata'), triggerdata)
WHERE id = @id
RETURNING *;
-- name: DeleteReaction :exec
DELETE
FROM reactions
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertTask :one
INSERT INTO tasks (id, name, open, owner, ticket, created, updated)
VALUES (@id, @name, @open, @owner, @ticket, @created, @updated)
RETURNING *;
-- name: CreateTask :one
INSERT INTO tasks (name, open, owner, ticket)
VALUES (@name, @open, @owner, @ticket)
RETURNING *;
-- name: UpdateTask :one
UPDATE tasks
SET name = coalesce(sqlc.narg('name'), name),
open = coalesce(sqlc.narg('open'), open),
owner = coalesce(sqlc.narg('owner'), owner)
WHERE id = @id
RETURNING *;
-- name: DeleteTask :exec
DELETE
FROM tasks
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertTimeline :one
INSERT INTO timeline (id, message, ticket, time, created, updated)
VALUES (@id, @message, @ticket, @time, @created, @updated)
RETURNING *;
-- name: CreateTimeline :one
INSERT INTO timeline (message, ticket, time)
VALUES (@message, @ticket, @time)
RETURNING *;
-- name: UpdateTimeline :one
UPDATE timeline
SET message = coalesce(sqlc.narg('message'), message),
time = coalesce(sqlc.narg('time'), time)
WHERE id = @id
RETURNING *;
-- name: DeleteTimeline :exec
DELETE
FROM timeline
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertType :one
INSERT INTO types (id, singular, plural, icon, schema, created, updated)
VALUES (@id, @singular, @plural, @icon, @schema, @created, @updated)
RETURNING *;
-- name: CreateType :one
INSERT INTO types (singular, plural, icon, schema)
VALUES (@singular, @plural, @icon, @schema)
RETURNING *;
-- name: UpdateType :one
UPDATE types
SET singular = coalesce(sqlc.narg('singular'), singular),
plural = coalesce(sqlc.narg('plural'), plural),
icon = coalesce(sqlc.narg('icon'), icon),
schema = coalesce(sqlc.narg('schema'), schema)
WHERE id = @id
RETURNING *;
-- name: DeleteType :exec
DELETE
FROM types
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertUser :one
INSERT INTO users (id, name, email, username, passwordHash, tokenKey, avatar, active, created, updated)
VALUES (@id, @name, @email, @username, @passwordHash, @tokenKey, @avatar, @active, @created, @updated)
RETURNING *;
-- name: CreateUser :one
INSERT INTO users (name, email, username, passwordHash, tokenKey, avatar, active)
VALUES (@name, @email, @username, @passwordHash, @tokenKey, @avatar, @active)
RETURNING *;
-- name: UpdateUser :one
UPDATE users
SET name = coalesce(sqlc.narg('name'), name),
email = coalesce(sqlc.narg('email'), email),
username = coalesce(sqlc.narg('username'), username),
passwordHash = coalesce(sqlc.narg('passwordHash'), passwordHash),
tokenKey = coalesce(sqlc.narg('tokenKey'), tokenKey),
avatar = coalesce(sqlc.narg('avatar'), avatar),
active = coalesce(sqlc.narg('active'), active),
lastResetSentAt = coalesce(sqlc.narg('lastResetSentAt'), lastResetSentAt),
lastVerificationSentAt = coalesce(sqlc.narg('lastVerificationSentAt'), lastVerificationSentAt)
WHERE id = @id
AND id != 'system'
RETURNING *;
-- name: DeleteUser :exec
DELETE
FROM users
WHERE id = @id
AND id != 'system';
------------------------------------------------------------------
-- name: InsertWebhook :one
INSERT INTO webhooks (id, name, collection, destination, created, updated)
VALUES (@id, @name, @collection, @destination, @created, @updated)
RETURNING *;
-- name: CreateWebhook :one
INSERT INTO webhooks (name, collection, destination)
VALUES (@name, @collection, @destination)
RETURNING *;
-- name: UpdateWebhook :one
UPDATE webhooks
SET name = coalesce(sqlc.narg('name'), name),
collection = coalesce(sqlc.narg('collection'), collection),
destination = coalesce(sqlc.narg('destination'), destination)
WHERE id = @id
RETURNING *;
-- name: DeleteWebhook :exec
DELETE
FROM webhooks
WHERE id = @id;
------------------------------------------------------------------
-- name: InsertGroup :one
INSERT INTO groups (id, name, permissions, created, updated)
VALUES (@id, @name, @permissions, @created, @updated)
RETURNING *;
-- name: CreateGroup :one
INSERT INTO groups (name, permissions)
VALUES (@name, @permissions)
RETURNING *;
-- name: UpdateGroup :one
UPDATE groups
SET name = coalesce(sqlc.narg('name'), name),
permissions = coalesce(sqlc.narg('permissions'), permissions)
WHERE id = @id
RETURNING *;
-- name: DeleteGroup :exec
DELETE
FROM groups
WHERE id = @id;
-- name: AssignGroupToUser :exec
INSERT INTO user_groups (user_id, group_id)
VALUES (@user_id, @group_id);
-- name: RemoveGroupFromUser :exec
DELETE
FROM user_groups
WHERE user_id = @user_id
AND group_id = @group_id;
-- name: AssignParentGroup :exec
INSERT INTO group_inheritance (parent_group_id, child_group_id)
VALUES (@parent_group_id, @child_group_id);
-- name: RemoveParentGroup :exec
DELETE
FROM group_inheritance
WHERE parent_group_id = @parent_group_id
AND child_group_id = @child_group_id;

17
app/hook/hook.go Normal file
View File

@@ -0,0 +1,17 @@
package hook
import "context"
type Hook struct {
subscribers []func(ctx context.Context, table string, record any)
}
func (h *Hook) Publish(ctx context.Context, table string, record any) {
for _, subscriber := range h.subscribers {
subscriber(ctx, table, record)
}
}
func (h *Hook) Subscribe(fn func(ctx context.Context, table string, record any)) {
h.subscribers = append(h.subscribers, fn)
}

126
app/hook/hook_test.go Normal file
View File

@@ -0,0 +1,126 @@
package hook
import (
"context"
"testing"
)
func TestHook_Publish(t *testing.T) {
t.Parallel()
type fields struct {
subscribers []func(ctx context.Context, table string, record any)
}
type args struct {
table string
record any
}
var called bool
subscriber := func(_ context.Context, _ string, _ any) {
called = true
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
name: "publish with no subscribers",
fields: fields{
subscribers: nil,
},
args: args{
table: "test_table",
record: "test_record",
},
want: false,
},
{
name: "publish with one subscriber",
fields: fields{
subscribers: []func(ctx context.Context, table string, record any){subscriber},
},
args: args{
table: "test_table",
record: "test_record",
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
called = false
h := &Hook{
subscribers: tt.fields.subscribers,
}
h.Publish(t.Context(), tt.args.table, tt.args.record)
if called != tt.want {
t.Errorf("Hook.Publish() called = %v, want %v", called, tt.want)
}
})
}
}
func TestHook_Subscribe(t *testing.T) {
t.Parallel()
type fields struct {
subscribers []func(ctx context.Context, table string, record any)
}
type args struct {
fn func(ctx context.Context, table string, record any)
}
subscriber := func(_ context.Context, _ string, _ any) {}
tests := []struct {
name string
fields fields
args args
want int
}{
{
name: "subscribe to empty hook",
fields: fields{
subscribers: nil,
},
args: args{
fn: subscriber,
},
want: 1,
},
{
name: "subscribe to hook with existing subscriber",
fields: fields{
subscribers: []func(ctx context.Context, table string, record any){subscriber},
},
args: args{
fn: subscriber,
},
want: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
h := &Hook{
subscribers: tt.fields.subscribers,
}
h.Subscribe(tt.args.fn)
if got := len(h.subscribers); got != tt.want {
t.Errorf("Hook.Subscribe() subscriber count = %v, want %v", got, tt.want)
}
})
}
}

25
app/hook/hooks.go Normal file
View File

@@ -0,0 +1,25 @@
package hook
type Hooks struct {
OnRecordsListRequest *Hook
OnRecordViewRequest *Hook
OnRecordBeforeCreateRequest *Hook
OnRecordAfterCreateRequest *Hook
OnRecordBeforeUpdateRequest *Hook
OnRecordAfterUpdateRequest *Hook
OnRecordBeforeDeleteRequest *Hook
OnRecordAfterDeleteRequest *Hook
}
func NewHooks() *Hooks {
return &Hooks{
OnRecordsListRequest: &Hook{},
OnRecordViewRequest: &Hook{},
OnRecordBeforeCreateRequest: &Hook{},
OnRecordAfterCreateRequest: &Hook{},
OnRecordBeforeUpdateRequest: &Hook{},
OnRecordAfterUpdateRequest: &Hook{},
OnRecordBeforeDeleteRequest: &Hook{},
OnRecordAfterDeleteRequest: &Hook{},
}
}

109
app/mail/mail.go Normal file
View File

@@ -0,0 +1,109 @@
package mail
import (
"cmp"
"context"
"fmt"
"log/slog"
"github.com/wneessen/go-mail"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/settings"
)
type Mailer struct {
queries *sqlc.Queries
}
func New(queries *sqlc.Queries) *Mailer {
return &Mailer{
queries: queries,
}
}
func (m *Mailer) Send(ctx context.Context, to, subject, plainTextBody, htmlBody string) error {
settings, err := settings.Load(ctx, m.queries)
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
if !settings.SMTP.Enabled {
return fmt.Errorf("SMTP is not enabled in settings")
}
if settings.SMTP.Host == "" || settings.SMTP.Username == "" || settings.SMTP.Password == "" {
return fmt.Errorf("SMTP settings are not configured properly: host, username, and password must be set")
}
client, err := mailClient(settings)
if err != nil {
return fmt.Errorf("failed to create mail client: %w", err)
}
message, err := createMessage(settings, to, subject, plainTextBody, htmlBody)
if err != nil {
return fmt.Errorf("failed to create mail message: %w", err)
}
if err := client.DialAndSend(message); err != nil {
return fmt.Errorf("failed to deliver mail: %w", err)
}
slog.InfoContext(ctx, "mail sent successfully", "to", to, "subject", subject)
return nil
}
func createMessage(settings *settings.Settings, to string, subject string, plainTextBody, htmlBody string) (*mail.Msg, error) {
message := mail.NewMsg()
if err := message.FromFormat(settings.Meta.SenderName, settings.Meta.SenderAddress); err != nil {
return nil, fmt.Errorf("failed to set FROM address: %w", err)
}
if err := message.To(to); err != nil {
return nil, fmt.Errorf("failed to set TO address: %w", err)
}
message.Subject(subject)
message.SetBodyString(mail.TypeTextPlain, plainTextBody)
if htmlBody != "" {
message.SetBodyString(mail.TypeTextHTML, htmlBody)
}
return message, nil
}
func mailClient(settings *settings.Settings) (*mail.Client, error) {
var authType mail.SMTPAuthType
if err := authType.UnmarshalString(cmp.Or(settings.SMTP.AuthMethod, "plain")); err != nil {
return nil, fmt.Errorf("failed to parse SMTP auth method: %w", err)
}
opts := []mail.Option{
mail.WithSMTPAuth(authType),
mail.WithUsername(settings.SMTP.Username),
mail.WithPassword(settings.SMTP.Password),
}
if settings.SMTP.Port != 0 {
opts = append(opts, mail.WithPort(settings.SMTP.Port))
}
if settings.SMTP.TLS {
opts = append(opts, mail.WithSSL())
}
if settings.SMTP.LocalName != "" {
opts = append(opts, mail.WithHELO(settings.SMTP.LocalName))
}
client, err := mail.NewClient(settings.SMTP.Host, opts...)
if err != nil {
return nil, fmt.Errorf("failed to create new mail delivery client: %w", err)
}
return client, nil
}

View File

@@ -0,0 +1,56 @@
package migration
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/upload"
)
type filesMigration struct{}
func newFilesMigration() func() (migration, error) {
return func() (migration, error) {
return filesMigration{}, nil
}
}
func (filesMigration) name() string { return "005_pocketbase_files_to_tusd" }
func (filesMigration) up(ctx context.Context, queries *sqlc.Queries, dir string, uploader *upload.Uploader) error {
oldUploadDir := filepath.Join(dir, "storage")
if _, err := os.Stat(oldUploadDir); os.IsNotExist(err) {
// If the old upload directory does not exist, we assume no migration is needed.
return nil
}
oldUploadRoot, err := os.OpenRoot(oldUploadDir)
if err != nil {
return fmt.Errorf("open old uploads root: %w", err)
}
files, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListFilesRow, error) {
return queries.ListFiles(ctx, sqlc.ListFilesParams{Limit: limit, Offset: offset})
})
if err != nil {
return fmt.Errorf("list files: %w", err)
}
for _, file := range files {
data, err := fs.ReadFile(oldUploadRoot.FS(), filepath.Join(file.ID, file.Blob))
if err != nil {
return fmt.Errorf("read file %s: %w", file.Blob, err)
}
if _, err := uploader.CreateFile(file.ID, file.Name, data); err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,49 @@
package migration
import (
"context"
"fmt"
"log/slog"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/upload"
)
type migration interface {
name() string
up(ctx context.Context, queries *sqlc.Queries, dir string, uploader *upload.Uploader) error
}
func Apply(ctx context.Context, queries *sqlc.Queries, dir string, uploader *upload.Uploader) error {
currentVersion, err := version(ctx, queries.WriteDB)
if err != nil {
return err
}
slog.InfoContext(ctx, "Current database version", "version", currentVersion)
migrations, err := migrations(currentVersion)
if err != nil {
return fmt.Errorf("failed to get migrations: %w", err)
}
if len(migrations) == 0 {
slog.InfoContext(ctx, "No migrations to apply")
return nil
}
for _, m := range migrations {
slog.InfoContext(ctx, "Applying migration", "name", m.name())
if err := m.up(ctx, queries, dir, uploader); err != nil {
return fmt.Errorf("migration %s failed: %w", m.name(), err)
}
}
if err := setVersion(ctx, queries.WriteDB, currentVersion+len(migrations)); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,22 @@
package migration
import (
"testing"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/upload"
)
func TestApply(t *testing.T) {
t.Parallel()
dir := t.TempDir()
queries := database.TestDB(t, dir)
uploader, err := upload.New(dir)
require.NoError(t, err)
require.NoError(t, Apply(t.Context(), queries, dir, uploader))
}

View File

@@ -0,0 +1,34 @@
package migration
import "fmt"
var migrationGenerators = []func() (migration, error){
newSQLMigration("000_create_pocketbase_tables"),
newSQLMigration("001_create_tables"),
newFilesMigration(),
newSQLMigration("002_create_defaultdata"),
newSQLMigration("003_create_groups"),
}
func migrations(version int) ([]migration, error) {
var migrations []migration
if version < 0 || version > len(migrationGenerators) {
return nil, fmt.Errorf("invalid migration version: %d", version)
}
if version == len(migrationGenerators) {
return migrations, nil // No migrations to apply
}
for _, migrationFunc := range migrationGenerators[version:] {
migration, err := migrationFunc()
if err != nil {
return nil, fmt.Errorf("failed to create migration: %w", err)
}
migrations = append(migrations, migration)
}
return migrations, nil
}

View File

@@ -0,0 +1,32 @@
package migration
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestMigrations_Success(t *testing.T) {
t.Parallel()
migs, err := migrations(0)
require.NoError(t, err)
require.Len(t, migs, len(migrationGenerators))
}
func TestMigrations_VersionOffset(t *testing.T) {
t.Parallel()
migs, err := migrations(1)
require.NoError(t, err)
require.Len(t, migs, len(migrationGenerators)-1)
}
func TestMigrations_Error(t *testing.T) {
t.Parallel()
migs, err := migrations(999) // Invalid version
require.Error(t, err)
require.Nil(t, migs)
require.Contains(t, err.Error(), "invalid migration version: 999")
}

42
app/migration/sql.go Normal file
View File

@@ -0,0 +1,42 @@
package migration
import (
"context"
"fmt"
sqlmigrations "github.com/SecurityBrewery/catalyst/app/database/migrations"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/upload"
)
type sqlMigration struct {
sqlName string
upSQL string
}
func newSQLMigration(name string) func() (migration, error) {
return func() (migration, error) {
up, err := sqlmigrations.Migrations.ReadFile(name + ".up.sql")
if err != nil {
return nil, fmt.Errorf("failed to read up migration file for %s: %w", name, err)
}
return &sqlMigration{
sqlName: name,
upSQL: string(up),
}, nil
}
}
func (m sqlMigration) name() string {
return m.sqlName
}
func (m sqlMigration) up(ctx context.Context, queries *sqlc.Queries, _ string, _ *upload.Uploader) error {
_, err := queries.WriteDB.ExecContext(ctx, m.upSQL)
if err != nil {
return fmt.Errorf("migration %s up failed: %w", m.sqlName, err)
}
return nil
}

41
app/migration/sql_test.go Normal file
View File

@@ -0,0 +1,41 @@
package migration
import (
"testing"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/upload"
)
func TestSQLMigration_UpAndDown(t *testing.T) {
t.Parallel()
m := sqlMigration{
sqlName: "test_migration",
upSQL: "CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);",
}
dir := t.TempDir()
queries := database.TestDB(t, dir)
uploader, err := upload.New(dir)
require.NoError(t, err)
// Test up
require.NoError(t, m.up(t.Context(), queries, dir, uploader))
// Table should exist
_, err = queries.WriteDB.ExecContext(t.Context(), "INSERT INTO test_table (name) VALUES ('foo')")
require.NoError(t, err)
}
func TestNewSQLMigration_FileNotFound(t *testing.T) {
t.Parallel()
f := newSQLMigration("does_not_exist")
_, err := f()
require.Error(t, err)
require.Contains(t, err.Error(), "failed to read up migration file")
}

27
app/migration/version.go Normal file
View File

@@ -0,0 +1,27 @@
package migration
import (
"context"
"database/sql"
"fmt"
)
func version(ctx context.Context, db *sql.DB) (int, error) {
// get the current version of the database
var currentVersion int
if err := db.QueryRowContext(ctx, "PRAGMA user_version").Scan(&currentVersion); err != nil {
return 0, fmt.Errorf("failed to get current database version: %w", err)
}
return currentVersion, nil
}
func setVersion(ctx context.Context, db *sql.DB, version int) error {
// Update the database version after successful migration
_, err := db.ExecContext(ctx, fmt.Sprintf("PRAGMA user_version = %d", version))
if err != nil {
return fmt.Errorf("failed to update database version: %w", err)
}
return nil
}

View File

@@ -0,0 +1,29 @@
package migration
import (
"database/sql"
"testing"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/require"
)
func TestVersionAndSetVersion(t *testing.T) {
t.Parallel()
db, err := sql.Open("sqlite3", ":memory:")
require.NoError(t, err, "failed to open in-memory db")
defer db.Close()
ver, err := version(t.Context(), db)
require.NoError(t, err, "failed to get version")
require.Equal(t, 0, ver, "expected version 0")
err = setVersion(t.Context(), db, 2)
require.NoError(t, err, "failed to set version")
ver, err = version(t.Context(), db)
require.NoError(t, err, "failed to get version after set")
require.Equal(t, 2, ver, "expected version 2")
}

8
app/openapi/config.yml Normal file
View File

@@ -0,0 +1,8 @@
package: openapi
generate:
chi-server: true
models: true
strict-server: true
output: app/openapi/gen.go
output-options:
skip-prune: true

7384
app/openapi/gen.go Normal file

File diff suppressed because it is too large Load Diff

15
app/pointer/pointer.go Normal file
View File

@@ -0,0 +1,15 @@
package pointer
func Pointer[T any](v T) *T {
return &v
}
func Dereference[T any](v *T) T {
if v == nil {
var zero T
return zero
}
return *v
}

View File

@@ -0,0 +1,34 @@
package pointer
import "testing"
func TestPointer(t *testing.T) {
t.Parallel()
v := 42
ptr := Pointer(v)
if ptr == nil {
t.Fatal("Pointer returned nil")
}
if *ptr != v {
t.Errorf("Pointer value = %v, want %v", *ptr, v)
}
}
func TestDereference(t *testing.T) {
t.Parallel()
v := 42
ptr := &v
if Dereference(ptr) != v {
t.Errorf("Dereference(ptr) = %v, want %v", Dereference(ptr), v)
}
var nilPtr *int
if Dereference(nilPtr) != 0 {
t.Errorf("Dereference(nil) = %v, want 0", Dereference(nilPtr))
}
}

View File

@@ -0,0 +1,72 @@
package action
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/SecurityBrewery/catalyst/app/auth"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/reaction/action/python"
"github.com/SecurityBrewery/catalyst/app/reaction/action/webhook"
)
func Run(ctx context.Context, url string, queries *sqlc.Queries, actionName string, actionData, payload json.RawMessage) ([]byte, error) {
action, err := decode(actionName, actionData)
if err != nil {
return nil, err
}
if a, ok := action.(authenticatedAction); ok {
token, err := systemToken(ctx, queries)
if err != nil {
return nil, fmt.Errorf("failed to get system token: %w", err)
}
a.SetEnv([]string{
"CATALYST_APP_URL=" + url,
"CATALYST_TOKEN=" + token,
})
}
return action.Run(ctx, payload)
}
type action interface {
Run(ctx context.Context, payload json.RawMessage) ([]byte, error)
}
type authenticatedAction interface {
SetEnv(env []string)
}
func decode(actionName string, actionData json.RawMessage) (action, error) {
switch actionName {
case "python":
var reaction python.Python
if err := json.Unmarshal(actionData, &reaction); err != nil {
return nil, err
}
return &reaction, nil
case "webhook":
var reaction webhook.Webhook
if err := json.Unmarshal(actionData, &reaction); err != nil {
return nil, err
}
return &reaction, nil
default:
return nil, fmt.Errorf("action %q not found", actionName)
}
}
func systemToken(ctx context.Context, queries *sqlc.Queries) (string, error) {
user, err := queries.SystemUser(ctx)
if err != nil {
return "", fmt.Errorf("failed to find system auth record: %w", err)
}
return auth.CreateAccessToken(ctx, &user, auth.All(), time.Hour, queries)
}

View File

@@ -0,0 +1,118 @@
package python
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strings"
)
type Python struct {
Requirements string `json:"requirements"`
Script string `json:"script"`
env []string
}
func (a *Python) SetEnv(env []string) {
a.env = env
}
func (a *Python) Run(ctx context.Context, payload json.RawMessage) ([]byte, error) {
tempDir, err := os.MkdirTemp("", "catalyst_action")
if err != nil {
return nil, err
}
defer os.RemoveAll(tempDir)
b, err := pythonSetup(ctx, tempDir)
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
b = append(b, ee.Stderr...)
}
return nil, fmt.Errorf("failed to setup python, %w: %s", err, string(b))
}
b, err = a.pythonInstallRequirements(ctx, tempDir)
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
b = append(b, ee.Stderr...)
}
return nil, fmt.Errorf("failed to run install requirements, %w: %s", err, string(b))
}
b, err = a.pythonRunScript(ctx, tempDir, string(payload))
if err != nil {
var ee *exec.ExitError
if errors.As(err, &ee) {
b = append(b, ee.Stderr...)
}
return nil, fmt.Errorf("failed to run script, %w: %s", err, string(b))
}
return b, nil
}
func pythonSetup(ctx context.Context, tempDir string) ([]byte, error) {
pythonPath, err := findExec("python3", "python")
if err != nil {
return nil, fmt.Errorf("python or python3 binary not found, %w", err)
}
// setup virtual environment
return exec.CommandContext(ctx, pythonPath, "-m", "venv", tempDir+"/venv").Output()
}
func (a *Python) pythonInstallRequirements(ctx context.Context, tempDir string) ([]byte, error) {
hasRequirements := len(strings.TrimSpace(a.Requirements)) > 0
if !hasRequirements {
return nil, nil
}
requirementsPath := tempDir + "/requirements.txt"
if err := os.WriteFile(requirementsPath, []byte(a.Requirements), 0o600); err != nil {
return nil, err
}
// install dependencies
pipPath := tempDir + "/venv/bin/pip"
return exec.CommandContext(ctx, pipPath, "install", "-r", requirementsPath).Output()
}
func (a *Python) pythonRunScript(ctx context.Context, tempDir, payload string) ([]byte, error) {
scriptPath := tempDir + "/script.py"
if err := os.WriteFile(scriptPath, []byte(a.Script), 0o600); err != nil {
return nil, err
}
pythonPath := tempDir + "/venv/bin/python"
cmd := exec.CommandContext(ctx, pythonPath, scriptPath, payload)
cmd.Env = a.env
return cmd.Output()
}
func findExec(name ...string) (string, error) {
for _, n := range name {
if p, err := exec.LookPath(n); err == nil {
return p, nil
}
}
return "", errors.New("no executable found")
}

View File

@@ -0,0 +1,104 @@
package python_test
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/SecurityBrewery/catalyst/app/reaction/action/python"
)
func TestPython_Run(t *testing.T) {
t.Parallel()
type fields struct {
Requirements string
Script string
}
type args struct {
payload string
}
tests := []struct {
name string
fields fields
args args
want []byte
wantErr assert.ErrorAssertionFunc
}{
{
name: "empty",
fields: fields{
Script: "pass",
},
args: args{
payload: "test",
},
want: []byte(""),
wantErr: assert.NoError,
},
{
name: "hello world",
fields: fields{
Script: "print('hello world')",
},
args: args{
payload: "test",
},
want: []byte("hello world\n"),
wantErr: assert.NoError,
},
{
name: "echo",
fields: fields{
Script: "import sys; print(sys.argv[1])",
},
args: args{
payload: "test",
},
want: []byte("test\n"),
wantErr: assert.NoError,
},
{
name: "error",
fields: fields{
Script: "import sys; sys.exit(1)",
},
args: args{
payload: "test",
},
want: nil,
wantErr: assert.Error,
},
{
name: "requests",
fields: fields{
Requirements: "requests",
Script: "import requests\nprint(requests.get('https://xkcd.com/2961/info.0.json').text)",
},
args: args{
payload: "test",
},
want: []byte("{\"month\": \"7\", \"num\": 2961, \"link\": \"\", \"year\": \"2024\", \"news\": \"\", \"safe_title\": \"CrowdStrike\", \"transcript\": \"\", \"alt\": \"We were going to try swordfighting, but all my compiling is on hold.\", \"img\": \"https://imgs.xkcd.com/comics/crowdstrike.png\", \"title\": \"CrowdStrike\", \"day\": \"19\"}\n"),
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctx := t.Context()
a := &python.Python{
Requirements: tt.fields.Requirements,
Script: tt.fields.Script,
}
got, err := a.Run(ctx, json.RawMessage(tt.args.payload))
tt.wantErr(t, err)
assert.Equal(t, tt.want, got)
})
}
}

View File

@@ -0,0 +1,20 @@
package webhook
import (
"encoding/base64"
"io"
"unicode/utf8"
)
func EncodeBody(requestBody io.Reader) (string, bool) {
body, err := io.ReadAll(requestBody)
if err != nil {
return "", false
}
if utf8.Valid(body) {
return string(body), false
}
return base64.StdEncoding.EncodeToString(body), true
}

View File

@@ -0,0 +1,55 @@
package webhook_test
import (
"bytes"
"io"
"testing"
"github.com/SecurityBrewery/catalyst/app/reaction/action/webhook"
)
func TestEncodeBody(t *testing.T) {
t.Parallel()
type args struct {
requestBody io.Reader
}
tests := []struct {
name string
args args
want string
want1 bool
}{
{
name: "utf8",
args: args{
requestBody: bytes.NewBufferString("body"),
},
want: "body",
want1: false,
},
{
name: "non-utf8",
args: args{
requestBody: bytes.NewBufferString("body\xe0"),
},
want: "Ym9keeA=",
want1: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, got1 := webhook.EncodeBody(tt.args.requestBody)
if got != tt.want {
t.Errorf("EncodeBody() got = %v, want %v", got, tt.want)
}
if got1 != tt.want1 {
t.Errorf("EncodeBody() got1 = %v, want %v", got1, tt.want1)
}
})
}
}

View File

@@ -0,0 +1,12 @@
package webhook
import (
"net/http"
)
type Response struct {
StatusCode int `json:"statusCode"`
Headers http.Header `json:"headers"`
Body string `json:"body"`
IsBase64Encoded bool `json:"isBase64Encoded"`
}

View File

@@ -0,0 +1,39 @@
package webhook
import (
"bytes"
"context"
"encoding/json"
"net/http"
)
type Webhook struct {
Headers map[string]string `json:"headers"`
URL string `json:"url"`
}
func (a *Webhook) Run(ctx context.Context, payload json.RawMessage) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, a.URL, bytes.NewReader(payload))
if err != nil {
return nil, err
}
for key, value := range a.Headers {
req.Header.Set(key, value)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, isBase64Encoded := EncodeBody(res.Body)
return json.Marshal(Response{
StatusCode: res.StatusCode,
Headers: res.Header,
Body: body,
IsBase64Encoded: isBase64Encoded,
})
}

View File

@@ -0,0 +1,85 @@
package webhook_test
import (
"encoding/json"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/sjson"
"github.com/SecurityBrewery/catalyst/app/reaction/action/webhook"
testing2 "github.com/SecurityBrewery/catalyst/testing"
)
func TestWebhook_Run(t *testing.T) {
t.Parallel()
server := testing2.NewRecordingServer()
go http.ListenAndServe("127.0.0.1:12347", server) //nolint:gosec,errcheck
if err := testing2.WaitForStatus("http://127.0.0.1:12347/health", http.StatusOK, 5*time.Second); err != nil {
t.Fatal(err)
}
type fields struct {
Headers map[string]string
URL string
}
type args struct {
payload string
}
tests := []struct {
name string
fields fields
args args
want map[string]any
wantErr assert.ErrorAssertionFunc
}{
{
name: "",
fields: fields{
Headers: map[string]string{},
URL: "http://127.0.0.1:12347/foo",
},
args: args{
payload: "test",
},
want: map[string]any{
"statusCode": 200,
"headers": map[string]any{
"Content-Length": []any{"13"},
"Content-Type": []any{"application/json; charset=UTF-8"},
},
"body": "{\"test\":true}",
"isBase64Encoded": false,
},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
a := &webhook.Webhook{
Headers: tt.fields.Headers,
URL: tt.fields.URL,
}
got, err := a.Run(t.Context(), json.RawMessage(tt.args.payload))
tt.wantErr(t, err)
want, err := json.Marshal(tt.want)
require.NoError(t, err)
got, err = sjson.DeleteBytes(got, "headers.Date")
require.NoError(t, err)
assert.JSONEq(t, string(want), string(got))
})
}
}

View File

@@ -0,0 +1,113 @@
package schedule
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"github.com/go-co-op/gocron/v2"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/reaction/action"
"github.com/SecurityBrewery/catalyst/app/settings"
)
type Scheduler struct {
scheduler gocron.Scheduler
queries *sqlc.Queries
}
type Schedule struct {
Expression string `json:"expression"`
}
func New(ctx context.Context, queries *sqlc.Queries) (*Scheduler, error) {
innerScheduler, err := gocron.NewScheduler()
if err != nil {
return nil, fmt.Errorf("failed to create scheduler: %w", err)
}
scheduler := &Scheduler{
scheduler: innerScheduler,
queries: queries,
}
if err := scheduler.loadJobs(ctx); err != nil {
return nil, fmt.Errorf("failed to load jobs: %w", err)
}
innerScheduler.Start()
return scheduler, nil
}
func (s *Scheduler) AddReaction(reaction *sqlc.Reaction) error {
var schedule Schedule
if err := json.Unmarshal(reaction.Triggerdata, &schedule); err != nil {
return fmt.Errorf("failed to unmarshal schedule data: %w", err)
}
_, err := s.scheduler.NewJob(
gocron.CronJob(schedule.Expression, false),
gocron.NewTask(
func(ctx context.Context) {
settings, err := settings.Load(ctx, s.queries)
if err != nil {
slog.ErrorContext(ctx, "Failed to load settings", "error", err)
return
}
_, err = action.Run(ctx, settings.Meta.AppURL, s.queries, reaction.Action, reaction.Actiondata, json.RawMessage("{}"))
if err != nil {
slog.ErrorContext(ctx, "Failed to run schedule reaction", "error", err, "reaction_id", reaction.ID)
}
},
),
gocron.WithTags(reaction.ID),
)
if err != nil {
return fmt.Errorf("failed to create new job for reaction %s: %w", reaction.ID, err)
}
return nil
}
func (s *Scheduler) RemoveReaction(id string) {
s.scheduler.RemoveByTags(id)
}
func (s *Scheduler) loadJobs(ctx context.Context) error {
reactions, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListReactionsByTriggerRow, error) {
return s.queries.ListReactionsByTrigger(ctx, sqlc.ListReactionsByTriggerParams{Trigger: "schedule", Limit: limit, Offset: offset})
})
if err != nil {
return fmt.Errorf("failed to find schedule reaction: %w", err)
}
if len(reactions) == 0 {
return nil
}
var errs []error
for _, reaction := range reactions {
if err := s.AddReaction(&sqlc.Reaction{
Action: reaction.Action,
Actiondata: reaction.Actiondata,
Created: reaction.Created,
ID: reaction.ID,
Name: reaction.Name,
Trigger: reaction.Trigger,
Triggerdata: reaction.Triggerdata,
Updated: reaction.Updated,
}); err != nil {
errs = append(errs, fmt.Errorf("failed to add reaction %s: %w", reaction.ID, err))
}
}
return errors.Join(errs...)
}

17
app/reaction/trigger.go Normal file
View File

@@ -0,0 +1,17 @@
package reaction
import (
"github.com/go-chi/chi/v5"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/hook"
reactionHook "github.com/SecurityBrewery/catalyst/app/reaction/trigger/hook"
"github.com/SecurityBrewery/catalyst/app/reaction/trigger/webhook"
)
func BindHooks(hooks *hook.Hooks, router chi.Router, queries *sqlc.Queries, test bool) error {
reactionHook.BindHooks(hooks, queries, test)
webhook.BindHooks(router, queries)
return nil
}

View File

@@ -0,0 +1,122 @@
package hook
import (
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"slices"
"github.com/SecurityBrewery/catalyst/app/auth/usercontext"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/hook"
"github.com/SecurityBrewery/catalyst/app/reaction/action"
"github.com/SecurityBrewery/catalyst/app/settings"
"github.com/SecurityBrewery/catalyst/app/webhook"
)
type Hook struct {
Collections []string `json:"collections"`
Events []string `json:"events"`
}
func BindHooks(hooks *hook.Hooks, queries *sqlc.Queries, test bool) {
hooks.OnRecordAfterCreateRequest.Subscribe(func(ctx context.Context, table string, record any) {
bindHook(ctx, queries, database.CreateAction, table, record, test)
})
hooks.OnRecordAfterUpdateRequest.Subscribe(func(ctx context.Context, table string, record any) {
bindHook(ctx, queries, database.UpdateAction, table, record, test)
})
hooks.OnRecordAfterDeleteRequest.Subscribe(func(ctx context.Context, table string, record any) {
bindHook(ctx, queries, database.DeleteAction, table, record, test)
})
}
func bindHook(ctx context.Context, queries *sqlc.Queries, event, collection string, record any, test bool) {
user, ok := usercontext.UserFromContext(ctx)
if !ok {
slog.ErrorContext(ctx, "failed to get user from session")
return
}
if !test {
go mustRunHook(context.Background(), queries, collection, event, record, user) //nolint:contextcheck
} else {
mustRunHook(ctx, queries, collection, event, record, user)
}
}
func mustRunHook(ctx context.Context, queries *sqlc.Queries, collection, event string, record any, auth *sqlc.User) {
if err := runHook(ctx, queries, collection, event, record, auth); err != nil {
slog.ErrorContext(ctx, fmt.Sprintf("failed to run hook reaction: %v", err))
}
}
func runHook(ctx context.Context, queries *sqlc.Queries, collection, event string, record any, auth *sqlc.User) error {
payload, err := json.Marshal(&webhook.Payload{
Action: event,
Collection: collection,
Record: record,
Auth: webhook.SanitizeUser(auth),
Admin: nil,
})
if err != nil {
return fmt.Errorf("failed to marshal webhook payload: %w", err)
}
hooks, err := findByHookTrigger(ctx, queries, collection, event)
if err != nil {
return fmt.Errorf("failed to find hook by trigger: %w", err)
}
if len(hooks) == 0 {
return nil
}
settings, err := settings.Load(ctx, queries)
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}
var errs []error
for _, hook := range hooks {
_, err = action.Run(ctx, settings.Meta.AppURL, queries, hook.Action, hook.Actiondata, payload)
if err != nil {
errs = append(errs, fmt.Errorf("failed to run hook reaction: %w", err))
}
}
return errors.Join(errs...)
}
func findByHookTrigger(ctx context.Context, queries *sqlc.Queries, collection, event string) ([]*sqlc.ListReactionsByTriggerRow, error) {
reactions, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListReactionsByTriggerRow, error) {
return queries.ListReactionsByTrigger(ctx, sqlc.ListReactionsByTriggerParams{Trigger: "hook", Limit: limit, Offset: offset})
})
if err != nil {
return nil, fmt.Errorf("failed to find hook reaction: %w", err)
}
if len(reactions) == 0 {
return nil, nil
}
var matchedRecords []*sqlc.ListReactionsByTriggerRow
for _, reaction := range reactions {
var hook Hook
if err := json.Unmarshal(reaction.Triggerdata, &hook); err != nil {
return nil, err
}
if slices.Contains(hook.Collections, collection) && slices.Contains(hook.Events, event) {
matchedRecords = append(matchedRecords, &reaction)
}
}
return matchedRecords, nil
}

View File

@@ -0,0 +1,15 @@
package webhook
import (
"net/http"
"net/url"
)
type Request struct {
Method string `json:"method"`
Path string `json:"path"`
Headers http.Header `json:"headers"`
Query url.Values `json:"query"`
Body string `json:"body"`
IsBase64Encoded bool `json:"isBase64Encoded"`
}

View File

@@ -0,0 +1,162 @@
package webhook
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/reaction/action"
"github.com/SecurityBrewery/catalyst/app/reaction/action/webhook"
"github.com/SecurityBrewery/catalyst/app/settings"
)
type Webhook struct {
Token string `json:"token"`
Path string `json:"path"`
}
const prefix = "/reaction/"
func BindHooks(router chi.Router, queries *sqlc.Queries) {
router.HandleFunc(prefix+"*", handle(queries))
}
func handle(queries *sqlc.Queries) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
reaction, payload, status, err := parseRequest(queries, r)
if err != nil {
http.Error(w, err.Error(), status)
return
}
settings, err := settings.Load(r.Context(), queries)
if err != nil {
http.Error(w, "failed to load settings: "+err.Error(), http.StatusInternalServerError)
return
}
output, err := action.Run(r.Context(), settings.Meta.AppURL, queries, reaction.Action, reaction.Actiondata, payload)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := writeOutput(w, output); err != nil {
slog.ErrorContext(r.Context(), "failed to write output", "error", err.Error())
}
}
}
func parseRequest(queries *sqlc.Queries, r *http.Request) (*sqlc.ListReactionsByTriggerRow, []byte, int, error) {
if !strings.HasPrefix(r.URL.Path, prefix) {
return nil, nil, http.StatusNotFound, fmt.Errorf("wrong prefix")
}
reactionName := strings.TrimPrefix(r.URL.Path, prefix)
reaction, trigger, found, err := findByWebhookTrigger(r.Context(), queries, reactionName)
if err != nil {
return nil, nil, http.StatusNotFound, err
}
if !found {
return nil, nil, http.StatusNotFound, fmt.Errorf("reaction not found")
}
if trigger.Token != "" {
auth := r.Header.Get("Authorization")
if !strings.HasPrefix(auth, "Bearer ") {
return nil, nil, http.StatusUnauthorized, fmt.Errorf("missing token")
}
if trigger.Token != strings.TrimPrefix(auth, "Bearer ") {
return nil, nil, http.StatusUnauthorized, fmt.Errorf("invalid token")
}
}
body, isBase64Encoded := webhook.EncodeBody(r.Body)
payload, err := json.Marshal(&Request{
Method: r.Method,
Path: r.URL.EscapedPath(),
Headers: r.Header,
Query: r.URL.Query(),
Body: body,
IsBase64Encoded: isBase64Encoded,
})
if err != nil {
return nil, nil, http.StatusInternalServerError, err
}
return reaction, payload, http.StatusOK, nil
}
func findByWebhookTrigger(ctx context.Context, queries *sqlc.Queries, path string) (*sqlc.ListReactionsByTriggerRow, *Webhook, bool, error) {
reactions, err := database.PaginateItems(ctx, func(ctx context.Context, offset, limit int64) ([]sqlc.ListReactionsByTriggerRow, error) {
return queries.ListReactionsByTrigger(ctx, sqlc.ListReactionsByTriggerParams{Trigger: "webhook", Limit: limit, Offset: offset})
})
if err != nil {
return nil, nil, false, err
}
if len(reactions) == 0 {
return nil, nil, false, nil
}
for _, reaction := range reactions {
var webhook Webhook
if err := json.Unmarshal(reaction.Triggerdata, &webhook); err != nil {
return nil, nil, false, err
}
if webhook.Path == path {
return &reaction, &webhook, true, nil
}
}
return nil, nil, false, nil
}
func writeOutput(w http.ResponseWriter, output []byte) error {
var catalystResponse webhook.Response
if err := json.Unmarshal(output, &catalystResponse); err == nil && catalystResponse.StatusCode != 0 {
for key, values := range catalystResponse.Headers {
for _, value := range values {
w.Header().Add(key, value)
}
}
if catalystResponse.IsBase64Encoded {
output, err = base64.StdEncoding.DecodeString(catalystResponse.Body)
if err != nil {
return fmt.Errorf("error decoding base64 body: %w", err)
}
} else {
output = []byte(catalystResponse.Body)
}
}
if json.Valid(output) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(output)
} else {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(output)
}
return nil
}

290
app/rootstore/rootstore.go Normal file
View File

@@ -0,0 +1,290 @@
package rootstore
import (
"context"
"crypto/rand"
"encoding/json"
"errors"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"github.com/tus/tusd/v2/pkg/handler"
)
var (
defaultFilePerm = os.FileMode(0o664)
defaultDirectoryPerm = os.FileMode(0o754)
)
const (
// StorageKeyPath is the key of the path of uploaded file in handler.FileInfo.Storage.
StorageKeyPath = "Path"
// StorageKeyInfoPath is the key of the path of .info file in handler.FileInfo.Storage.
StorageKeyInfoPath = "InfoPath"
)
// RootStore is a file system based data store for tusd.
type RootStore struct {
root *os.Root
}
func New(root *os.Root) RootStore {
return RootStore{root: root}
}
// UseIn sets this store as the core data store in the passed composer and adds
// all possible extension to it.
func (store RootStore) UseIn(composer *handler.StoreComposer) {
composer.UseCore(store)
composer.UseTerminater(store)
composer.UseConcater(store)
composer.UseLengthDeferrer(store)
composer.UseContentServer(store)
}
func (store RootStore) NewUpload(_ context.Context, info handler.FileInfo) (handler.Upload, error) {
if info.ID == "" {
info.ID = rand.Text()
}
// The .info file's location can directly be deduced from the upload ID
infoPath := store.infoPath(info.ID)
// The binary file's location might be modified by the pre-create hook.
var binPath string
if info.Storage != nil && info.Storage[StorageKeyPath] != "" {
binPath = info.Storage[StorageKeyPath]
} else {
binPath = store.defaultBinPath(info.ID)
}
info.Storage = map[string]string{
"Type": "rootstore",
StorageKeyPath: binPath,
StorageKeyInfoPath: infoPath,
}
_ = store.root.MkdirAll(filepath.Dir(binPath), defaultDirectoryPerm)
// Create binary file with no content
if err := store.root.WriteFile(binPath, nil, defaultFilePerm); err != nil {
return nil, err
}
upload := &fileUpload{
root: store.root,
info: info,
infoPath: infoPath,
binPath: binPath,
}
// writeInfo creates the file by itself if necessary
if err := upload.writeInfo(); err != nil {
return nil, err
}
return upload, nil
}
func (store RootStore) GetUpload(_ context.Context, id string) (handler.Upload, error) {
infoPath := store.infoPath(id)
data, err := fs.ReadFile(store.root.FS(), filepath.ToSlash(infoPath))
if err != nil {
if os.IsNotExist(err) {
// Interpret os.ErrNotExist as 404 Not Found
err = handler.ErrNotFound
}
return nil, err
}
var info handler.FileInfo
if err := json.Unmarshal(data, &info); err != nil {
return nil, err
}
// If the info file contains a custom path to the binary file, we use that. If not, we
// fall back to the default value (although the Path property should always be set in recent
// tusd versions).
var binPath string
if info.Storage != nil && info.Storage[StorageKeyPath] != "" {
// No filepath.Join here because the joining already happened in NewUpload. Duplicate joining
// with relative paths lead to incorrect paths
binPath = info.Storage[StorageKeyPath]
} else {
binPath = store.defaultBinPath(info.ID)
}
stat, err := store.root.Stat(binPath)
if err != nil {
if os.IsNotExist(err) {
// Interpret os.ErrNotExist as 404 Not Found
err = handler.ErrNotFound
}
return nil, err
}
info.Offset = stat.Size()
return &fileUpload{
root: store.root,
info: info,
binPath: binPath,
infoPath: infoPath,
}, nil
}
func (store RootStore) AsTerminatableUpload(upload handler.Upload) handler.TerminatableUpload {
return upload.(*fileUpload) //nolint:forcetypeassert
}
func (store RootStore) AsLengthDeclarableUpload(upload handler.Upload) handler.LengthDeclarableUpload {
return upload.(*fileUpload) //nolint:forcetypeassert
}
func (store RootStore) AsConcatableUpload(upload handler.Upload) handler.ConcatableUpload {
return upload.(*fileUpload) //nolint:forcetypeassert
}
func (store RootStore) AsServableUpload(upload handler.Upload) handler.ServableUpload {
return upload.(*fileUpload) //nolint:forcetypeassert
}
// defaultBinPath returns the path to the file storing the binary data, if it is
// not customized using the pre-create hook.
func (store RootStore) defaultBinPath(id string) string {
return id
}
// infoPath returns the path to the .info file storing the file's info.
func (store RootStore) infoPath(id string) string {
return id + ".info"
}
type fileUpload struct {
root *os.Root
// info stores the current information about the upload
info handler.FileInfo
// infoPath is the path to the .info file
infoPath string
// binPath is the path to the binary file (which has no extension)
binPath string
}
func (upload *fileUpload) GetInfo(_ context.Context) (handler.FileInfo, error) {
return upload.info, nil
}
func (upload *fileUpload) WriteChunk(_ context.Context, _ int64, src io.Reader) (int64, error) {
file, err := upload.root.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm)
if err != nil {
return 0, err
}
// Avoid the use of defer file.Close() here to ensure no errors are lost
// See https://github.com/tus/tusd/issues/698.
n, err := io.Copy(file, src)
upload.info.Offset += n
if err != nil {
file.Close()
return n, err
}
return n, file.Close()
}
func (upload *fileUpload) GetReader(_ context.Context) (io.ReadCloser, error) {
return upload.root.Open(upload.binPath)
}
func (upload *fileUpload) Terminate(_ context.Context) error {
// We ignore errors indicating that the files cannot be found because we want
// to delete them anyways. The files might be removed by a cron job for cleaning up
// or some file might have been removed when tusd crashed during the termination.
err := upload.root.Remove(upload.binPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
err = upload.root.Remove(upload.infoPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}
func (upload *fileUpload) ConcatUploads(_ context.Context, uploads []handler.Upload) (err error) {
file, err := upload.root.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm)
if err != nil {
return err
}
defer func() {
// Ensure that close error is propagated, if it occurs.
// See https://github.com/tus/tusd/issues/698.
cerr := file.Close()
if err == nil {
err = cerr
}
}()
for _, partialUpload := range uploads {
if err := partialUpload.(*fileUpload).appendTo(file); err != nil { //nolint:forcetypeassert
return err
}
}
return
}
func (upload *fileUpload) appendTo(file *os.File) error {
src, err := upload.root.Open(upload.binPath)
if err != nil {
return err
}
if _, err := io.Copy(file, src); err != nil {
src.Close()
return err
}
return src.Close()
}
func (upload *fileUpload) DeclareLength(_ context.Context, length int64) error {
upload.info.Size = length
upload.info.SizeIsDeferred = false
return upload.writeInfo()
}
// writeInfo updates the entire information. Everything will be overwritten.
func (upload *fileUpload) writeInfo() error {
data, err := json.Marshal(upload.info)
if err != nil {
return err
}
_ = upload.root.MkdirAll(filepath.Dir(upload.infoPath), defaultDirectoryPerm)
return upload.root.WriteFile(upload.infoPath, data, defaultFilePerm)
}
func (upload *fileUpload) FinishUpload(_ context.Context) error {
return nil
}
func (upload *fileUpload) ServeContent(_ context.Context, w http.ResponseWriter, r *http.Request) error {
http.ServeFileFS(w, r, upload.root.FS(), filepath.ToSlash(upload.binPath))
return nil
}

View File

@@ -0,0 +1,391 @@
package rootstore
import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tus/tusd/v2/pkg/handler"
)
// Test interface implementation of FSStore.
var (
_ handler.DataStore = RootStore{}
_ handler.TerminaterDataStore = RootStore{}
_ handler.ConcaterDataStore = RootStore{}
_ handler.LengthDeferrerDataStore = RootStore{}
)
func TestFSStore(t *testing.T) {
t.Parallel()
root, err := os.OpenRoot(t.TempDir())
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
// Create new upload
upload, err := store.NewUpload(ctx, handler.FileInfo{
Size: 42,
MetaData: map[string]string{
"hello": "world",
},
})
require.NoError(t, err)
assert.NotNil(t, upload)
// Check info without writing
info, err := upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 0, info.Offset)
assert.Equal(t, handler.MetaData{"hello": "world"}, info.MetaData)
assert.Len(t, info.Storage, 3)
assert.Equal(t, "rootstore", info.Storage["Type"])
assert.Equal(t, info.ID, info.Storage["Path"])
assert.Equal(t, info.ID+".info", info.Storage["InfoPath"])
// Write data to upload
bytesWritten, err := upload.WriteChunk(ctx, 0, strings.NewReader("hello world"))
require.NoError(t, err)
assert.EqualValues(t, len("hello world"), bytesWritten)
// Check new offset
info, err = upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 11, info.Offset)
// Read content
reader, err := upload.GetReader(ctx)
require.NoError(t, err)
content, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, "hello world", string(content))
reader.Close()
// Serve content
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Header.Set("Range", "bytes=0-4")
err = store.AsServableUpload(upload).ServeContent(t.Context(), w, r)
require.NoError(t, err)
assert.Equal(t, http.StatusPartialContent, w.Code)
assert.Equal(t, "5", w.Header().Get("Content-Length"))
assert.Equal(t, "text/plain; charset=utf-8", w.Header().Get("Content-Type"))
assert.Equal(t, "bytes 0-4/11", w.Header().Get("Content-Range"))
assert.NotEmpty(t, w.Header().Get("Last-Modified"))
assert.Equal(t, "hello", w.Body.String())
// Terminate upload
require.NoError(t, store.AsTerminatableUpload(upload).Terminate(ctx))
// Test if upload is deleted
upload, err = store.GetUpload(ctx, info.ID)
assert.Nil(t, upload)
assert.Equal(t, handler.ErrNotFound, err)
}
// TestCreateDirectories tests whether an upload with a slash in its ID causes
// the correct directories to be created.
func TestFSStoreCreateDirectories(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
root, err := os.OpenRoot(tmp)
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
// Create new upload
upload, err := store.NewUpload(ctx, handler.FileInfo{
ID: "hello/world/123",
Size: 42,
MetaData: map[string]string{
"hello": "world",
},
})
require.NoError(t, err)
assert.NotNil(t, upload)
// Check info without writing
info, err := upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 0, info.Offset)
assert.Equal(t, handler.MetaData{"hello": "world"}, info.MetaData)
assert.Len(t, info.Storage, 3)
assert.Equal(t, "rootstore", info.Storage["Type"])
assert.Equal(t, filepath.FromSlash(info.ID), info.Storage["Path"])
assert.Equal(t, filepath.FromSlash(info.ID+".info"), info.Storage["InfoPath"])
// Write data to upload
bytesWritten, err := upload.WriteChunk(ctx, 0, strings.NewReader("hello world"))
require.NoError(t, err)
assert.EqualValues(t, len("hello world"), bytesWritten)
// Check new offset
info, err = upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 11, info.Offset)
// Read content
reader, err := upload.GetReader(ctx)
require.NoError(t, err)
content, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, "hello world", string(content))
reader.Close()
// Check that the file and directory exists on disk
statInfo, err := os.Stat(filepath.Join(tmp, "hello/world/123"))
require.NoError(t, err)
assert.True(t, statInfo.Mode().IsRegular())
assert.EqualValues(t, 11, statInfo.Size())
statInfo, err = os.Stat(filepath.Join(tmp, "hello/world/"))
require.NoError(t, err)
assert.True(t, statInfo.Mode().IsDir())
// Terminate upload
require.NoError(t, store.AsTerminatableUpload(upload).Terminate(ctx))
// Test if upload is deleted
upload, err = store.GetUpload(ctx, info.ID)
assert.Nil(t, upload)
assert.Equal(t, handler.ErrNotFound, err)
}
func TestFSStoreNotFound(t *testing.T) {
t.Parallel()
root, err := os.OpenRoot(t.TempDir())
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
upload, err := store.GetUpload(ctx, "upload-that-does-not-exist")
require.Error(t, err)
assert.Equal(t, handler.ErrNotFound, err)
assert.Nil(t, upload)
}
func TestFSStoreConcatUploads(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
root, err := os.OpenRoot(tmp)
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
// Create new upload to hold concatenated upload
finUpload, err := store.NewUpload(ctx, handler.FileInfo{Size: 9})
require.NoError(t, err)
assert.NotNil(t, finUpload)
finInfo, err := finUpload.GetInfo(ctx)
require.NoError(t, err)
finID := finInfo.ID
// Create three uploads for concatenating
partialUploads := make([]handler.Upload, 3)
contents := []string{
"abc",
"def",
"ghi",
}
for i := range 3 {
upload, err := store.NewUpload(ctx, handler.FileInfo{Size: 3})
require.NoError(t, err)
n, err := upload.WriteChunk(ctx, 0, strings.NewReader(contents[i]))
require.NoError(t, err)
assert.EqualValues(t, 3, n)
partialUploads[i] = upload
}
err = store.AsConcatableUpload(finUpload).ConcatUploads(ctx, partialUploads)
require.NoError(t, err)
// Check offset
finUpload, err = store.GetUpload(ctx, finID)
require.NoError(t, err)
info, err := finUpload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 9, info.Size)
assert.EqualValues(t, 9, info.Offset)
// Read content
reader, err := finUpload.GetReader(ctx)
require.NoError(t, err)
content, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, "abcdefghi", string(content))
reader.Close()
}
func TestFSStoreDeclareLength(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
root, err := os.OpenRoot(tmp)
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
upload, err := store.NewUpload(ctx, handler.FileInfo{
Size: 0,
SizeIsDeferred: true,
})
require.NoError(t, err)
assert.NotNil(t, upload)
info, err := upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 0, info.Size)
assert.True(t, info.SizeIsDeferred)
err = store.AsLengthDeclarableUpload(upload).DeclareLength(ctx, 100)
require.NoError(t, err)
updatedInfo, err := upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 100, updatedInfo.Size)
assert.False(t, updatedInfo.SizeIsDeferred)
}
// TestCustomRelativePath tests whether the upload's destination can be customized
// relative to the storage directory.
func TestFSStoreCustomRelativePath(t *testing.T) {
t.Parallel()
tmp := t.TempDir()
root, err := os.OpenRoot(tmp)
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
ctx := t.Context()
// Create new upload
upload, err := store.NewUpload(ctx, handler.FileInfo{
ID: "folder1/info",
Size: 42,
Storage: map[string]string{
"Path": "./folder2/bin",
},
})
require.NoError(t, err)
assert.NotNil(t, upload)
// Check info without writing
info, err := upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 0, info.Offset)
assert.Len(t, info.Storage, 3)
assert.Equal(t, "rootstore", info.Storage["Type"])
assert.Equal(t, filepath.FromSlash("./folder2/bin"), info.Storage["Path"])
assert.Equal(t, filepath.FromSlash("folder1/info.info"), info.Storage["InfoPath"])
// Write data to upload
bytesWritten, err := upload.WriteChunk(ctx, 0, strings.NewReader("hello world"))
require.NoError(t, err)
assert.EqualValues(t, len("hello world"), bytesWritten)
// Check new offset
info, err = upload.GetInfo(ctx)
require.NoError(t, err)
assert.EqualValues(t, 42, info.Size)
assert.EqualValues(t, 11, info.Offset)
// Read content
reader, err := upload.GetReader(ctx)
require.NoError(t, err)
content, err := io.ReadAll(reader)
require.NoError(t, err)
assert.Equal(t, "hello world", string(content))
reader.Close()
// Check that the output file and info file exist on disk
statInfo, err := os.Stat(filepath.Join(tmp, "folder2/bin"))
require.NoError(t, err)
assert.True(t, statInfo.Mode().IsRegular())
assert.EqualValues(t, 11, statInfo.Size())
statInfo, err = os.Stat(filepath.Join(tmp, "folder1/info.info"))
require.NoError(t, err)
assert.True(t, statInfo.Mode().IsRegular())
// Terminate upload
require.NoError(t, store.AsTerminatableUpload(upload).Terminate(ctx))
// Test if upload is deleted
upload, err = store.GetUpload(ctx, info.ID)
assert.Nil(t, upload)
assert.Equal(t, handler.ErrNotFound, err)
}
// TestCustomAbsolutePath tests whether the upload's destination can be customized
// using an absolute path to the storage directory.
func TestFSStoreCustomAbsolutePath(t *testing.T) {
t.Parallel()
root, err := os.OpenRoot(t.TempDir())
require.NoError(t, err)
t.Cleanup(func() { root.Close() })
store := New(root)
// Create new upload, but the Path property points to a directory
// outside of the directory given to FSStore
binPath := filepath.Join(t.TempDir(), "dir/my-upload.bin")
_, err = store.NewUpload(t.Context(), handler.FileInfo{
ID: "my-upload",
Size: 42,
Storage: map[string]string{
"Path": binPath,
},
})
require.Error(t, err)
_, err = os.Stat(binPath)
require.Error(t, err)
}

79
app/router/demomode.go Normal file
View File

@@ -0,0 +1,79 @@
package router
import (
"context"
"log/slog"
"net/http"
"slices"
"strings"
"github.com/SecurityBrewery/catalyst/app/database"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
)
func demoMode(queries *sqlc.Queries) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if isCriticalPath(r) && isCriticalMethod(r) && isDemoMode(r.Context(), queries) {
http.Error(w, "Cannot modify reactions or files in demo mode", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
func isCriticalPath(r *http.Request) bool {
// Define critical paths that should not be accessed in demo mode
criticalPaths := []string{
"/api/files",
"/api/groups",
"/api/reactions",
"/api/settings",
"/api/users",
"/api/webhooks",
}
for _, path := range criticalPaths {
if strings.Contains(r.URL.Path, path) {
return true
}
}
return false
}
func isCriticalMethod(r *http.Request) bool {
return !slices.Contains([]string{http.MethodHead, http.MethodGet}, r.Method)
}
func isDemoMode(ctx context.Context, queries *sqlc.Queries) bool {
var demoMode bool
if err := database.Paginate(ctx, func(ctx context.Context, offset, limit int64) (nextPage bool, err error) {
slog.InfoContext(ctx, "Checking for demo mode", "offset", offset, "limit", limit)
features, err := queries.ListFeatures(ctx, sqlc.ListFeaturesParams{Offset: offset, Limit: limit})
if err != nil {
return false, err
}
for _, feature := range features {
if feature.Key == "demo" {
demoMode = true
return false, nil // Stop pagination if demo mode is found
}
}
return len(features) > 0, nil
}); err != nil {
slog.ErrorContext(ctx, "Failed to check demo mode", "error", err)
return false
}
return demoMode
}

115
app/router/demomode_test.go Normal file
View File

@@ -0,0 +1,115 @@
package router
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/SecurityBrewery/catalyst/app/data"
)
func Test_isCriticalPath(t *testing.T) {
t.Parallel()
tests := []struct {
path string
want bool
}{
{"/api/reactions/1", true},
{"/api/files/1", true},
{"/api/other", false},
}
for _, tt := range tests {
req := httptest.NewRequest(http.MethodGet, tt.path, nil)
assert.Equal(t, tt.want, isCriticalPath(req))
}
}
func Test_isCriticalMethod(t *testing.T) {
t.Parallel()
tests := []struct {
method string
want bool
}{
{http.MethodPost, true},
{http.MethodPut, true},
{http.MethodGet, false},
{http.MethodHead, false},
}
for _, tt := range tests {
req := httptest.NewRequest(tt.method, "/", nil)
assert.Equal(t, tt.want, isCriticalMethod(req))
}
}
func Test_isDemoMode(t *testing.T) {
t.Parallel()
queries := data.NewTestDB(t, t.TempDir())
assert.False(t, isDemoMode(t.Context(), queries))
_, err := queries.CreateFeature(t.Context(), "demo")
require.NoError(t, err)
assert.True(t, isDemoMode(t.Context(), queries))
}
func Test_demoModeMiddleware(t *testing.T) {
t.Parallel()
queries := data.NewTestDB(t, t.TempDir())
mw := demoMode(queries)
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
nextCalled = true
w.WriteHeader(http.StatusTeapot)
})
// not demo mode
rr := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/api/reactions", nil).WithContext(t.Context())
mw(next).ServeHTTP(rr, req)
assert.True(t, nextCalled)
assert.Equal(t, http.StatusTeapot, rr.Code)
// enable demo mode
_, err := queries.CreateFeature(t.Context(), "demo")
require.NoError(t, err)
nextCalled = false
rr = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/api/reactions", nil).WithContext(t.Context())
mw(next).ServeHTTP(rr, req)
assert.False(t, nextCalled)
assert.Equal(t, http.StatusForbidden, rr.Code)
// non critical path
nextCalled = false
rr = httptest.NewRecorder()
req = httptest.NewRequest(http.MethodPost, "/api/other", nil).WithContext(t.Context())
mw(next).ServeHTTP(rr, req)
assert.True(t, nextCalled)
assert.Equal(t, http.StatusTeapot, rr.Code)
}
func Test_handlers(t *testing.T) {
t.Parallel()
queries := data.NewTestDB(t, t.TempDir())
// healthHandler
healthRR := httptest.NewRecorder()
healthReq := httptest.NewRequest(http.MethodGet, "/health", nil).WithContext(t.Context())
healthHandler(queries)(healthRR, healthReq)
assert.Equal(t, http.StatusOK, healthRR.Code)
assert.Equal(t, "OK", healthRR.Body.String())
}

37
app/router/http.go Normal file
View File

@@ -0,0 +1,37 @@
package router
import (
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"github.com/SecurityBrewery/catalyst/ui"
)
func staticFiles(w http.ResponseWriter, r *http.Request) {
if devServer := os.Getenv("UI_DEVSERVER"); devServer != "" {
u, _ := url.Parse(devServer)
r.Host = r.URL.Host
httputil.NewSingleHostReverseProxy(u).ServeHTTP(w, r)
return
}
vueStatic(w, r)
}
func vueStatic(w http.ResponseWriter, r *http.Request) {
handler := http.FileServer(http.FS(ui.UI()))
if strings.HasPrefix(r.URL.Path, "/ui/assets/") {
handler = http.StripPrefix("/ui", handler)
} else {
r.URL.Path = "/"
}
handler.ServeHTTP(w, r)
}

30
app/router/http_test.go Normal file
View File

@@ -0,0 +1,30 @@
package router
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestStaticFiles_DevServer(t *testing.T) {
t.Setenv("UI_DEVSERVER", "http://localhost:1234")
rec := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/ui/assets/test.js", nil)
// This will try to proxy, but since the dev server isn't running, it should not panic
// We just want to make sure it doesn't crash
staticFiles(rec, r)
}
func TestStaticFiles_VueStatic(t *testing.T) {
t.Parallel()
rec := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/ui/assets/test.js", nil)
staticFiles(rec, r)
// Should not panic, and should serve something (even if it's a 404)
if rec.Result().StatusCode == 0 {
t.Error("expected a status code from vueStatic")
}
}

69
app/router/router.go Normal file
View File

@@ -0,0 +1,69 @@
package router
import (
"log/slog"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/google/martian/v3/cors"
"github.com/SecurityBrewery/catalyst/app/auth"
"github.com/SecurityBrewery/catalyst/app/database/sqlc"
"github.com/SecurityBrewery/catalyst/app/mail"
"github.com/SecurityBrewery/catalyst/app/service"
"github.com/SecurityBrewery/catalyst/app/upload"
)
func New(service *service.Service, queries *sqlc.Queries, uploader *upload.Uploader, mailer *mail.Mailer) (*chi.Mux, error) {
r := chi.NewRouter()
// middleware for the router
r.Use(func(next http.Handler) http.Handler {
return http.Handler(cors.NewHandler(next))
})
r.Use(demoMode(queries))
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Timeout(time.Second * 60))
r.Use(middleware.Recoverer)
// base routes
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/ui/", http.StatusFound)
})
r.Get("/ui/*", staticFiles)
r.Get("/health", healthHandler(queries))
// auth routes
r.Mount("/auth", auth.Server(queries, mailer))
// API routes
r.With(auth.Middleware(queries)).Mount("/api", http.StripPrefix("/api", service))
uploadHandler, err := tusRoutes(queries, uploader)
if err != nil {
return nil, err
}
r.Mount("/files", http.StripPrefix("/files", uploadHandler))
return r, nil
}
func healthHandler(queries *sqlc.Queries) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if _, err := queries.ListFeatures(r.Context(), sqlc.ListFeaturesParams{Offset: 0, Limit: 100}); err != nil {
slog.ErrorContext(r.Context(), "Failed to get flags", "error", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}
}

Some files were not shown because too many files have changed in this diff Show More